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