AccountManager.java revision 756b735e9312ee52618158270f0bdd0ec691a712
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 extras 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 extras) { 245 try { 246 return mService.addAccount(account, password, extras); 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 try { 324 return mService.peekAuthToken(account, authTokenType); 325 } catch (RemoteException e) { 326 // won't ever happen 327 throw new RuntimeException(e); 328 } 329 } 330 331 /** 332 * Sets the password for the account. The password may be null. If the account does not exist 333 * then this call has no affect. 334 * <p> 335 * Requires that the caller has permission 336 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 337 * with the same UID as the Authenticator for the account. 338 * @param account the account whose password is to be set. Must not be null. 339 * @param password the password to set for the account. May be null. 340 */ 341 public void setPassword(final Account account, final String password) { 342 try { 343 mService.setPassword(account, password); 344 } catch (RemoteException e) { 345 // won't ever happen 346 throw new RuntimeException(e); 347 } 348 } 349 350 /** 351 * Sets the password for account to null. If the account does not exist then this call 352 * has no effect. 353 * <p> 354 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 355 * @param account the account whose password is to be cleared. Must not be null. 356 */ 357 public void clearPassword(final Account account) { 358 try { 359 mService.clearPassword(account); 360 } catch (RemoteException e) { 361 // won't ever happen 362 throw new RuntimeException(e); 363 } 364 } 365 366 /** 367 * Sets account's userdata named "key" to the specified value. If the account does not 368 * exist then this call has no effect. 369 * <p> 370 * Requires that the caller has permission 371 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 372 * with the same UID as the Authenticator for the account. 373 * @param account the account whose userdata is to be set. Must not be null. 374 * @param key the key of the userdata to set. Must not be null. 375 * @param value the value to set. May be null. 376 */ 377 public void setUserData(final Account account, final String key, final String value) { 378 try { 379 mService.setUserData(account, key, value); 380 } catch (RemoteException e) { 381 // won't ever happen 382 throw new RuntimeException(e); 383 } 384 } 385 386 /** 387 * Sets the authtoken named by "authTokenType" to the value specified by authToken. 388 * If the account does not exist then this call has no effect. 389 * <p> 390 * Requires that the caller has permission 391 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 392 * with the same UID as the Authenticator for the account. 393 * @param account the account whose authtoken is to be set. Must not be null. 394 * @param authTokenType the type of the authtoken to set. Must not be null. 395 * @param authToken the authToken to set. May be null. 396 */ 397 public void setAuthToken(Account account, final String authTokenType, final String authToken) { 398 try { 399 mService.setAuthToken(account, authTokenType, authToken); 400 } catch (RemoteException e) { 401 // won't ever happen 402 throw new RuntimeException(e); 403 } 404 } 405 406 /** 407 * Convenience method that makes a blocking call to 408 * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)} 409 * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result. 410 * <p> 411 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 412 * @param account the account whose authtoken is to be retrieved, must not be null 413 * @param authTokenType the type of authtoken to retrieve 414 * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification 415 * for the account if no authtoken is cached by the AccountManager and the the authenticator 416 * does not have valid credentials to get an authtoken. 417 * @return an authtoken for the given account and authTokenType, if one is cached by the 418 * AccountManager, null otherwise. 419 * @throws AuthenticatorException if the authenticator is not present, unreachable or returns 420 * an invalid response. 421 * @throws OperationCanceledException if the request is canceled for any reason 422 * @throws java.io.IOException if the authenticator experiences an IOException while attempting 423 * to communicate with its backend server. 424 */ 425 public String blockingGetAuthToken(Account account, String authTokenType, 426 boolean notifyAuthFailure) 427 throws OperationCanceledException, IOException, AuthenticatorException { 428 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 429 null /* handler */).getResult(); 430 return bundle.getString(KEY_AUTHTOKEN); 431 } 432 433 /** 434 * Request that an authtoken of the specified type be returned for an account. 435 * If the Account Manager has a cached authtoken of the requested type then it will 436 * service the request itself. Otherwise it will pass the request on to the authenticator. 437 * The authenticator can try to service this request with information it already has stored 438 * in the AccountManager but may need to launch an activity to prompt the 439 * user to enter credentials. If it is able to retrieve the authtoken it will be returned 440 * in the result. 441 * <p> 442 * If the authenticator needs to prompt the user for credentials it will return an intent to 443 * the activity that will do the prompting. If an activity is supplied then that activity 444 * will be used to launch the intent and the result will come from it. Otherwise a result will 445 * be returned that contains the intent. 446 * <p> 447 * This call returns immediately but runs asynchronously and the result is accessed via the 448 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 449 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 450 * method asynchronously then they will generally pass in a callback object that will get 451 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 452 * they will generally pass null for the callback and instead call 453 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 454 * which will then block until the request completes. 455 * <p> 456 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 457 * 458 * @param account The account whose credentials are to be updated. 459 * @param authTokenType the auth token to retrieve as part of updating the credentials. 460 * May be null. 461 * @param loginOptions authenticator specific options for the request 462 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 463 * the intent will be started with this activity. If activity is null then the result will 464 * be returned as-is. 465 * @param callback A callback to invoke when the request completes. If null then 466 * no callback is invoked. 467 * @param handler The {@link Handler} to use to invoke the callback. If null then the 468 * main thread's {@link Handler} is used. 469 * @return an {@link AccountManagerFuture} that represents the future result of the call. 470 * The future result is a {@link Bundle} that contains: 471 * <ul> 472 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} 473 * </ul> 474 * If the user presses "back" then the request will be canceled. 475 */ 476 public AccountManagerFuture<Bundle> getAuthToken( 477 final Account account, final String authTokenType, final Bundle loginOptions, 478 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 479 if (activity == null) throw new IllegalArgumentException("activity is null"); 480 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 481 return new AmsTask(activity, handler, callback) { 482 public void doWork() throws RemoteException { 483 mService.getAuthToken(mResponse, account, authTokenType, 484 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 485 loginOptions); 486 } 487 }.start(); 488 } 489 490 /** 491 * Request that an authtoken of the specified type be returned for an account. 492 * If the Account Manager has a cached authtoken of the requested type then it will 493 * service the request itself. Otherwise it will pass the request on to the authenticator. 494 * The authenticator can try to service this request with information it already has stored 495 * in the AccountManager but may need to launch an activity to prompt the 496 * user to enter credentials. If it is able to retrieve the authtoken it will be returned 497 * in the result. 498 * <p> 499 * If the authenticator needs to prompt the user for credentials it will return an intent for 500 * an activity that will do the prompting. If an intent is returned and notifyAuthFailure 501 * is true then a notification will be created that launches this intent. 502 * <p> 503 * This call returns immediately but runs asynchronously and the result is accessed via the 504 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 505 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 506 * method asynchronously then they will generally pass in a callback object that will get 507 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 508 * they will generally pass null for the callback and instead call 509 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 510 * which will then block until the request completes. 511 * <p> 512 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 513 * 514 * @param account The account whose credentials are to be updated. 515 * @param authTokenType the auth token to retrieve as part of updating the credentials. 516 * May be null. 517 * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the 518 * result then a "sign-on needed" notification will be created that will launch this intent. 519 * @param callback A callback to invoke when the request completes. If null then 520 * no callback is invoked. 521 * @param handler The {@link Handler} to use to invoke the callback. If null then the 522 * main thread's {@link Handler} is used. 523 * @return an {@link AccountManagerFuture} that represents the future result of the call. 524 * The future result is a {@link Bundle} that contains either: 525 * <ul> 526 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 527 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} 528 * if the authenticator is able to retrieve the auth token 529 * </ul> 530 * If the user presses "back" then the request will be canceled. 531 */ 532 public AccountManagerFuture<Bundle> getAuthToken( 533 final Account account, final String authTokenType, final boolean notifyAuthFailure, 534 AccountManagerCallback<Bundle> callback, Handler handler) { 535 if (account == null) throw new IllegalArgumentException("account is null"); 536 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 537 return new AmsTask(null, handler, callback) { 538 public void doWork() throws RemoteException { 539 mService.getAuthToken(mResponse, account, authTokenType, 540 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 541 } 542 }.start(); 543 } 544 545 /** 546 * Request that an account be added with the given accountType. This request 547 * is processed by the authenticator for the account type. If no authenticator is registered 548 * in the system then {@link AuthenticatorException} is thrown. 549 * <p> 550 * This call returns immediately but runs asynchronously and the result is accessed via the 551 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 552 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 553 * method asynchronously then they will generally pass in a callback object that will get 554 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 555 * they will generally pass null for the callback and instead call 556 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 557 * which will then block until the request completes. 558 * <p> 559 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 560 * 561 * @param accountType The type of account to add. This must not be null. 562 * @param authTokenType The account that is added should be able to service this auth token 563 * type. This may be null. 564 * @param requiredFeatures The account that is added should support these features. 565 * This array may be null or empty. 566 * @param addAccountOptions A bundle of authenticator-specific options that is passed on 567 * to the authenticator. This may be null. 568 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 569 * the intent will be started with this activity. If activity is null then the result will 570 * be returned as-is. 571 * @param callback A callback to invoke when the request completes. If null then 572 * no callback is invoked. 573 * @param handler The {@link Handler} to use to invoke the callback. If null then the 574 * main thread's {@link Handler} is used. 575 * @return an {@link AccountManagerFuture} that represents the future result of the call. 576 * The future result is a {@link Bundle} that contains either: 577 * <ul> 578 * <li> {@link #KEY_INTENT}, or 579 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} 580 * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified). 581 * </ul> 582 */ 583 public AccountManagerFuture<Bundle> addAccount(final String accountType, 584 final String authTokenType, final String[] requiredFeatures, 585 final Bundle addAccountOptions, 586 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 587 return new AmsTask(activity, handler, callback) { 588 public void doWork() throws RemoteException { 589 mService.addAcount(mResponse, accountType, authTokenType, 590 requiredFeatures, activity != null, addAccountOptions); 591 } 592 }.start(); 593 } 594 595 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 596 final String type, final String[] features, 597 AccountManagerCallback<Account[]> callback, Handler handler) { 598 if (type == null) throw new IllegalArgumentException("type is null"); 599 return new Future2Task<Account[]>(handler, callback) { 600 public void doWork() throws RemoteException { 601 mService.getAccountsByFeatures(mResponse, type, features); 602 } 603 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 604 if (!bundle.containsKey(KEY_ACCOUNTS)) { 605 throw new AuthenticatorException("no result in response"); 606 } 607 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); 608 Account[] descs = new Account[parcelables.length]; 609 for (int i = 0; i < parcelables.length; i++) { 610 descs[i] = (Account) parcelables[i]; 611 } 612 return descs; 613 } 614 }.start(); 615 } 616 617 /** 618 * Requests that the authenticator checks that the user knows the credentials for the account. 619 * This is typically done by returning an intent to an activity that prompts the user to 620 * enter the credentials. This request 621 * is processed by the authenticator for the account. If no matching authenticator is 622 * registered in the system then {@link AuthenticatorException} is thrown. 623 * <p> 624 * This call returns immediately but runs asynchronously and the result is accessed via the 625 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 626 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 627 * method asynchronously then they will generally pass in a callback object that will get 628 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 629 * they will generally pass null for the callback and instead call 630 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 631 * which will then block until the request completes. 632 * <p> 633 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 634 * 635 * @param account The account whose credentials are to be checked 636 * @param options authenticator specific options for the request 637 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 638 * the intent will be started with this activity. If activity is null then the result will 639 * be returned as-is. 640 * @param callback A callback to invoke when the request completes. If null then 641 * no callback is invoked. 642 * @param handler The {@link Handler} to use to invoke the callback. If null then the 643 * main thread's {@link Handler} is used. 644 * @return an {@link AccountManagerFuture} that represents the future result of the call. 645 * The future result is a {@link Bundle} that contains either: 646 * <ul> 647 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 648 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 649 * credentials 650 * </ul> 651 * If the user presses "back" then the request will be canceled. 652 */ 653 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, 654 final Bundle options, 655 final Activity activity, 656 final AccountManagerCallback<Bundle> callback, 657 final Handler handler) { 658 return new AmsTask(activity, handler, callback) { 659 public void doWork() throws RemoteException { 660 mService.confirmCredentials(mResponse, account, options, activity != null); 661 } 662 }.start(); 663 } 664 665 /** 666 * Requests that the authenticator update the the credentials for a user. This is typically 667 * done by returning an intent to an activity that will prompt the user to update the stored 668 * credentials for the account. This request 669 * is processed by the authenticator for the account. If no matching authenticator is 670 * registered in the system then {@link AuthenticatorException} is thrown. 671 * <p> 672 * This call returns immediately but runs asynchronously and the result is accessed via the 673 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 674 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 675 * method asynchronously then they will generally pass in a callback object that will get 676 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 677 * they will generally pass null for the callback and instead call 678 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 679 * which will then block until the request completes. 680 * <p> 681 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 682 * 683 * @param account The account whose credentials are to be updated. 684 * @param authTokenType the auth token to retrieve as part of updating the credentials. 685 * May be null. 686 * @param loginOptions authenticator specific options for the request 687 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 688 * the intent will be started with this activity. If activity is null then the result will 689 * be returned as-is. 690 * @param callback A callback to invoke when the request completes. If null then 691 * no callback is invoked. 692 * @param handler The {@link Handler} to use to invoke the callback. If null then the 693 * main thread's {@link Handler} is used. 694 * @return an {@link AccountManagerFuture} that represents the future result of the call. 695 * The future result is a {@link Bundle} that contains either: 696 * <ul> 697 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 698 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 699 * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided. 700 * </ul> 701 * If the user presses "back" then the request will be canceled. 702 */ 703 public AccountManagerFuture<Bundle> updateCredentials(final Account account, 704 final String authTokenType, 705 final Bundle loginOptions, final Activity activity, 706 final AccountManagerCallback<Bundle> callback, 707 final Handler handler) { 708 return new AmsTask(activity, handler, callback) { 709 public void doWork() throws RemoteException { 710 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 711 loginOptions); 712 } 713 }.start(); 714 } 715 716 /** 717 * Request that the properties for an authenticator be updated. This is typically done by 718 * returning an intent to an activity that will allow the user to make changes. This request 719 * is processed by the authenticator for the account. If no matching authenticator is 720 * registered in the system then {@link AuthenticatorException} is thrown. 721 * <p> 722 * This call returns immediately but runs asynchronously and the result is accessed via the 723 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 724 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 725 * method asynchronously then they will generally pass in a callback object that will get 726 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 727 * they will generally pass null for the callback and instead call 728 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 729 * which will then block until the request completes. 730 * <p> 731 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 732 * 733 * @param accountType The account type of the authenticator whose properties are to be edited. 734 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 735 * the intent will be started with this activity. If activity is null then the result will 736 * be returned as-is. 737 * @param callback A callback to invoke when the request completes. If null then 738 * no callback is invoked. 739 * @param handler The {@link Handler} to use to invoke the callback. If null then the 740 * main thread's {@link Handler} is used. 741 * @return an {@link AccountManagerFuture} that represents the future result of the call. 742 * The future result is a {@link Bundle} that contains either: 743 * <ul> 744 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 745 * <li> nothing, returned if the edit completes successfully 746 * </ul> 747 * If the user presses "back" then the request will be canceled. 748 */ 749 public AccountManagerFuture<Bundle> editProperties(final String accountType, 750 final Activity activity, final AccountManagerCallback<Bundle> callback, 751 final Handler handler) { 752 return new AmsTask(activity, handler, callback) { 753 public void doWork() throws RemoteException { 754 mService.editProperties(mResponse, accountType, activity != null); 755 } 756 }.start(); 757 } 758 759 private void ensureNotOnMainThread() { 760 final Looper looper = Looper.myLooper(); 761 if (looper != null && looper == mContext.getMainLooper()) { 762 // We really want to throw an exception here, but GTalkService exercises this 763 // path quite a bit and needs some serious rewrite in order to work properly. 764 //noinspection ThrowableInstanceNeverThrow 765// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 766// new Exception()); 767 // TODO(fredq) remove the log and throw this exception when the callers are fixed 768// throw new IllegalStateException( 769// "calling this from your main thread can lead to deadlock"); 770 } 771 } 772 773 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 774 final AccountManagerFuture<Bundle> future) { 775 handler = handler == null ? mMainHandler : handler; 776 handler.post(new Runnable() { 777 public void run() { 778 callback.run(future); 779 } 780 }); 781 } 782 783 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener, 784 final Account[] accounts) { 785 final Account[] accountsCopy = new Account[accounts.length]; 786 // send a copy to make sure that one doesn't 787 // change what another sees 788 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 789 handler = (handler == null) ? mMainHandler : handler; 790 handler.post(new Runnable() { 791 public void run() { 792 try { 793 listener.onAccountsUpdated(accountsCopy); 794 } catch (SQLException e) { 795 // Better luck next time. If the problem was disk-full, 796 // the STORAGE_OK intent will re-trigger the update. 797 Log.e(TAG, "Can't update accounts", e); 798 } 799 } 800 }); 801 } 802 803 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 804 final IAccountManagerResponse mResponse; 805 final Handler mHandler; 806 final AccountManagerCallback<Bundle> mCallback; 807 final Activity mActivity; 808 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 809 super(new Callable<Bundle>() { 810 public Bundle call() throws Exception { 811 throw new IllegalStateException("this should never be called"); 812 } 813 }); 814 815 mHandler = handler; 816 mCallback = callback; 817 mActivity = activity; 818 mResponse = new Response(); 819 } 820 821 public final AccountManagerFuture<Bundle> start() { 822 try { 823 doWork(); 824 } catch (RemoteException e) { 825 setException(e); 826 } 827 return this; 828 } 829 830 public abstract void doWork() throws RemoteException; 831 832 private Bundle internalGetResult(Long timeout, TimeUnit unit) 833 throws OperationCanceledException, IOException, AuthenticatorException { 834 ensureNotOnMainThread(); 835 try { 836 if (timeout == null) { 837 return get(); 838 } else { 839 return get(timeout, unit); 840 } 841 } catch (CancellationException e) { 842 throw new OperationCanceledException(); 843 } catch (TimeoutException e) { 844 // fall through and cancel 845 } catch (InterruptedException e) { 846 // fall through and cancel 847 } catch (ExecutionException e) { 848 final Throwable cause = e.getCause(); 849 if (cause instanceof IOException) { 850 throw (IOException) cause; 851 } else if (cause instanceof UnsupportedOperationException) { 852 throw new AuthenticatorException(cause); 853 } else if (cause instanceof AuthenticatorException) { 854 throw (AuthenticatorException) cause; 855 } else if (cause instanceof RuntimeException) { 856 throw (RuntimeException) cause; 857 } else if (cause instanceof Error) { 858 throw (Error) cause; 859 } else { 860 throw new IllegalStateException(cause); 861 } 862 } finally { 863 cancel(true /* interrupt if running */); 864 } 865 throw new OperationCanceledException(); 866 } 867 868 public Bundle getResult() 869 throws OperationCanceledException, IOException, AuthenticatorException { 870 return internalGetResult(null, null); 871 } 872 873 public Bundle getResult(long timeout, TimeUnit unit) 874 throws OperationCanceledException, IOException, AuthenticatorException { 875 return internalGetResult(timeout, unit); 876 } 877 878 protected void done() { 879 if (mCallback != null) { 880 postToHandler(mHandler, mCallback, this); 881 } 882 } 883 884 /** Handles the responses from the AccountManager */ 885 private class Response extends IAccountManagerResponse.Stub { 886 public void onResult(Bundle bundle) { 887 Intent intent = bundle.getParcelable("intent"); 888 if (intent != null && mActivity != null) { 889 // since the user provided an Activity we will silently start intents 890 // that we see 891 mActivity.startActivity(intent); 892 // leave the Future running to wait for the real response to this request 893 } else if (bundle.getBoolean("retry")) { 894 try { 895 doWork(); 896 } catch (RemoteException e) { 897 // this will only happen if the system process is dead, which means 898 // we will be dying ourselves 899 } 900 } else { 901 set(bundle); 902 } 903 } 904 905 public void onError(int code, String message) { 906 if (code == ERROR_CODE_CANCELED) { 907 // the authenticator indicated that this request was canceled, do so now 908 cancel(true /* mayInterruptIfRunning */); 909 return; 910 } 911 setException(convertErrorToException(code, message)); 912 } 913 } 914 915 } 916 917 private abstract class BaseFutureTask<T> extends FutureTask<T> { 918 final public IAccountManagerResponse mResponse; 919 final Handler mHandler; 920 921 public BaseFutureTask(Handler handler) { 922 super(new Callable<T>() { 923 public T call() throws Exception { 924 throw new IllegalStateException("this should never be called"); 925 } 926 }); 927 mHandler = handler; 928 mResponse = new Response(); 929 } 930 931 public abstract void doWork() throws RemoteException; 932 933 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 934 935 protected void postRunnableToHandler(Runnable runnable) { 936 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 937 handler.post(runnable); 938 } 939 940 protected void startTask() { 941 try { 942 doWork(); 943 } catch (RemoteException e) { 944 setException(e); 945 } 946 } 947 948 protected class Response extends IAccountManagerResponse.Stub { 949 public void onResult(Bundle bundle) { 950 try { 951 T result = bundleToResult(bundle); 952 if (result == null) { 953 return; 954 } 955 set(result); 956 return; 957 } catch (ClassCastException e) { 958 // we will set the exception below 959 } catch (AuthenticatorException e) { 960 // we will set the exception below 961 } 962 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response"); 963 } 964 965 public void onError(int code, String message) { 966 if (code == ERROR_CODE_CANCELED) { 967 cancel(true /* mayInterruptIfRunning */); 968 return; 969 } 970 setException(convertErrorToException(code, message)); 971 } 972 } 973 } 974 975 private abstract class Future2Task<T> 976 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 977 final AccountManagerCallback<T> mCallback; 978 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 979 super(handler); 980 mCallback = callback; 981 } 982 983 protected void done() { 984 if (mCallback != null) { 985 postRunnableToHandler(new Runnable() { 986 public void run() { 987 mCallback.run(Future2Task.this); 988 } 989 }); 990 } 991 } 992 993 public Future2Task<T> start() { 994 startTask(); 995 return this; 996 } 997 998 private T internalGetResult(Long timeout, TimeUnit unit) 999 throws OperationCanceledException, IOException, AuthenticatorException { 1000 ensureNotOnMainThread(); 1001 try { 1002 if (timeout == null) { 1003 return get(); 1004 } else { 1005 return get(timeout, unit); 1006 } 1007 } catch (InterruptedException e) { 1008 // fall through and cancel 1009 } catch (TimeoutException e) { 1010 // fall through and cancel 1011 } catch (CancellationException e) { 1012 // fall through and cancel 1013 } catch (ExecutionException e) { 1014 final Throwable cause = e.getCause(); 1015 if (cause instanceof IOException) { 1016 throw (IOException) cause; 1017 } else if (cause instanceof UnsupportedOperationException) { 1018 throw new AuthenticatorException(cause); 1019 } else if (cause instanceof AuthenticatorException) { 1020 throw (AuthenticatorException) cause; 1021 } else if (cause instanceof RuntimeException) { 1022 throw (RuntimeException) cause; 1023 } else if (cause instanceof Error) { 1024 throw (Error) cause; 1025 } else { 1026 throw new IllegalStateException(cause); 1027 } 1028 } finally { 1029 cancel(true /* interrupt if running */); 1030 } 1031 throw new OperationCanceledException(); 1032 } 1033 1034 public T getResult() 1035 throws OperationCanceledException, IOException, AuthenticatorException { 1036 return internalGetResult(null, null); 1037 } 1038 1039 public T getResult(long timeout, TimeUnit unit) 1040 throws OperationCanceledException, IOException, AuthenticatorException { 1041 return internalGetResult(timeout, unit); 1042 } 1043 1044 } 1045 1046 private Exception convertErrorToException(int code, String message) { 1047 if (code == ERROR_CODE_NETWORK_ERROR) { 1048 return new IOException(message); 1049 } 1050 1051 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) { 1052 return new UnsupportedOperationException(message); 1053 } 1054 1055 if (code == ERROR_CODE_INVALID_RESPONSE) { 1056 return new AuthenticatorException(message); 1057 } 1058 1059 if (code == ERROR_CODE_BAD_ARGUMENTS) { 1060 return new IllegalArgumentException(message); 1061 } 1062 1063 return new AuthenticatorException(message); 1064 } 1065 1066 private class GetAuthTokenByTypeAndFeaturesTask 1067 extends AmsTask implements AccountManagerCallback<Bundle> { 1068 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 1069 final String[] features, Activity activityForPrompting, 1070 final Bundle addAccountOptions, final Bundle loginOptions, 1071 AccountManagerCallback<Bundle> callback, Handler handler) { 1072 super(activityForPrompting, handler, callback); 1073 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1074 mAccountType = accountType; 1075 mAuthTokenType = authTokenType; 1076 mFeatures = features; 1077 mAddAccountOptions = addAccountOptions; 1078 mLoginOptions = loginOptions; 1079 mMyCallback = this; 1080 } 1081 volatile AccountManagerFuture<Bundle> mFuture = null; 1082 final String mAccountType; 1083 final String mAuthTokenType; 1084 final String[] mFeatures; 1085 final Bundle mAddAccountOptions; 1086 final Bundle mLoginOptions; 1087 final AccountManagerCallback<Bundle> mMyCallback; 1088 1089 public void doWork() throws RemoteException { 1090 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 1091 new AccountManagerCallback<Account[]>() { 1092 public void run(AccountManagerFuture<Account[]> future) { 1093 Account[] accounts; 1094 try { 1095 accounts = future.getResult(); 1096 } catch (OperationCanceledException e) { 1097 setException(e); 1098 return; 1099 } catch (IOException e) { 1100 setException(e); 1101 return; 1102 } catch (AuthenticatorException e) { 1103 setException(e); 1104 return; 1105 } 1106 1107 if (accounts.length == 0) { 1108 if (mActivity != null) { 1109 // no accounts, add one now. pretend that the user directly 1110 // made this request 1111 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 1112 mAddAccountOptions, mActivity, mMyCallback, mHandler); 1113 } else { 1114 // send result since we can't prompt to add an account 1115 Bundle result = new Bundle(); 1116 result.putString(KEY_ACCOUNT_NAME, null); 1117 result.putString(KEY_ACCOUNT_TYPE, null); 1118 result.putString(KEY_AUTHTOKEN, null); 1119 try { 1120 mResponse.onResult(result); 1121 } catch (RemoteException e) { 1122 // this will never happen 1123 } 1124 // we are done 1125 } 1126 } else if (accounts.length == 1) { 1127 // have a single account, return an authtoken for it 1128 if (mActivity == null) { 1129 mFuture = getAuthToken(accounts[0], mAuthTokenType, 1130 false /* notifyAuthFailure */, mMyCallback, mHandler); 1131 } else { 1132 mFuture = getAuthToken(accounts[0], 1133 mAuthTokenType, mLoginOptions, 1134 mActivity, mMyCallback, mHandler); 1135 } 1136 } else { 1137 if (mActivity != null) { 1138 IAccountManagerResponse chooseResponse = 1139 new IAccountManagerResponse.Stub() { 1140 public void onResult(Bundle value) throws RemoteException { 1141 Account account = new Account( 1142 value.getString(KEY_ACCOUNT_NAME), 1143 value.getString(KEY_ACCOUNT_TYPE)); 1144 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 1145 mActivity, mMyCallback, mHandler); 1146 } 1147 1148 public void onError(int errorCode, String errorMessage) 1149 throws RemoteException { 1150 mResponse.onError(errorCode, errorMessage); 1151 } 1152 }; 1153 // have many accounts, launch the chooser 1154 Intent intent = new Intent(); 1155 intent.setClassName("android", 1156 "android.accounts.ChooseAccountActivity"); 1157 intent.putExtra(KEY_ACCOUNTS, accounts); 1158 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE, 1159 new AccountManagerResponse(chooseResponse)); 1160 mActivity.startActivity(intent); 1161 // the result will arrive via the IAccountManagerResponse 1162 } else { 1163 // send result since we can't prompt to select an account 1164 Bundle result = new Bundle(); 1165 result.putString(KEY_ACCOUNTS, null); 1166 try { 1167 mResponse.onResult(result); 1168 } catch (RemoteException e) { 1169 // this will never happen 1170 } 1171 // we are done 1172 } 1173 } 1174 }}, mHandler); 1175 } 1176 1177 public void run(AccountManagerFuture<Bundle> future) { 1178 try { 1179 set(future.getResult()); 1180 } catch (OperationCanceledException e) { 1181 cancel(true /* mayInterruptIfRUnning */); 1182 } catch (IOException e) { 1183 setException(e); 1184 } catch (AuthenticatorException e) { 1185 setException(e); 1186 } 1187 } 1188 } 1189 1190 /** 1191 * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures}, 1192 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)}, 1193 * and {@link #addAccount}. It first gets the list of accounts that match accountType and the 1194 * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType 1195 * feature set, and addAccountOptions. If there is exactly one then 1196 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is 1197 * called with that account. If there are more than one then a chooser activity is launched 1198 * to prompt the user to select one of them and then the authtoken is retrieved for it, 1199 * <p> 1200 * This call returns immediately but runs asynchronously and the result is accessed via the 1201 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 1202 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 1203 * method asynchronously then they will generally pass in a callback object that will get 1204 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 1205 * they will generally pass null for the callback and instead call 1206 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 1207 * which will then block until the request completes. 1208 * <p> 1209 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1210 * 1211 * @param accountType the accountType to query; this must be non-null 1212 * @param authTokenType the type of authtoken to retrieve; this must be non-null 1213 * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}. 1214 * @param activityForPrompting The activity used to start any account management 1215 * activities that are required to fulfill this request. This may be null. 1216 * @param addAccountOptions authenticator-specific options used if an account needs to be added 1217 * @param loginOptions authenticator-specific options passed to getAuthToken 1218 * @param callback A callback to invoke when the request completes. If null then 1219 * no callback is invoked. 1220 * @param handler The {@link Handler} to use to invoke the callback. If null then the 1221 * main thread's {@link Handler} is used. 1222 * @return an {@link AccountManagerFuture} that represents the future result of the call. 1223 * The future result is a {@link Bundle} that contains either: 1224 * <ul> 1225 * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to 1226 * fulfill the request. 1227 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the 1228 * request completes successfully. 1229 * </ul> 1230 * If the user presses "back" then the request will be canceled. 1231 */ 1232 public AccountManagerFuture<Bundle> getAuthTokenByFeatures( 1233 final String accountType, final String authTokenType, final String[] features, 1234 final Activity activityForPrompting, final Bundle addAccountOptions, 1235 final Bundle loginOptions, 1236 final AccountManagerCallback<Bundle> callback, final Handler handler) { 1237 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1238 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1239 final GetAuthTokenByTypeAndFeaturesTask task = 1240 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 1241 activityForPrompting, addAccountOptions, loginOptions, callback, handler); 1242 task.start(); 1243 return task; 1244 } 1245 1246 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners = 1247 Maps.newHashMap(); 1248 1249 /** 1250 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 1251 * so that it can read the updated list of accounts and send them to the listener 1252 * in mAccountsUpdatedListeners. 1253 */ 1254 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 1255 public void onReceive(final Context context, final Intent intent) { 1256 final Account[] accounts = getAccounts(); 1257 // send the result to the listeners 1258 synchronized (mAccountsUpdatedListeners) { 1259 for (Map.Entry<OnAccountsUpdateListener, Handler> entry : 1260 mAccountsUpdatedListeners.entrySet()) { 1261 postToHandler(entry.getValue(), entry.getKey(), accounts); 1262 } 1263 } 1264 } 1265 }; 1266 1267 /** 1268 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. 1269 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 1270 * in or the main thread's Handler if handler is null. 1271 * <p> 1272 * You must remove this listener before the context that was used to retrieve this 1273 * {@link AccountManager} instance goes away. This generally means when the Activity 1274 * or Service you are running is stopped. 1275 * @param listener the listener to add 1276 * @param handler the Handler whose thread will be used to invoke the listener. If null 1277 * the AccountManager context's main thread will be used. 1278 * @param updateImmediately if true then the listener will be invoked as a result of this 1279 * call. 1280 * @throws IllegalArgumentException if listener is null 1281 * @throws IllegalStateException if listener was already added 1282 */ 1283 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, 1284 Handler handler, boolean updateImmediately) { 1285 if (listener == null) { 1286 throw new IllegalArgumentException("the listener is null"); 1287 } 1288 synchronized (mAccountsUpdatedListeners) { 1289 if (mAccountsUpdatedListeners.containsKey(listener)) { 1290 throw new IllegalStateException("this listener is already added"); 1291 } 1292 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1293 1294 mAccountsUpdatedListeners.put(listener, handler); 1295 1296 if (wasEmpty) { 1297 // Register a broadcast receiver to monitor account changes 1298 IntentFilter intentFilter = new IntentFilter(); 1299 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); 1300 // To recover from disk-full. 1301 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1302 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1303 } 1304 } 1305 1306 if (updateImmediately) { 1307 postToHandler(handler, listener, getAccounts()); 1308 } 1309 } 1310 1311 /** 1312 * Remove an {@link OnAccountsUpdateListener} that was previously registered with 1313 * {@link #addOnAccountsUpdatedListener}. 1314 * @param listener the listener to remove 1315 * @throws IllegalArgumentException if listener is null 1316 * @throws IllegalStateException if listener was not already added 1317 */ 1318 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) { 1319 if (listener == null) { 1320 throw new IllegalArgumentException("the listener is null"); 1321 } 1322 synchronized (mAccountsUpdatedListeners) { 1323 if (!mAccountsUpdatedListeners.containsKey(listener)) { 1324 throw new IllegalStateException("this listener was not previously added"); 1325 } 1326 mAccountsUpdatedListeners.remove(listener); 1327 if (mAccountsUpdatedListeners.isEmpty()) { 1328 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1329 } 1330 } 1331 } 1332} 1333