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