AccountManager.java revision 3084a6f80180506ce26fe4773d9a19f004b7f625
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 it will return an intent for 564 * an activity that will do the prompting. If an intent is returned and notifyAuthFailure 565 * is true then a notification will be created that launches this intent. 566 * <p> 567 * This call returns immediately but runs asynchronously and the result is accessed via the 568 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 569 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 570 * method asynchronously then they will generally pass in a callback object that will get 571 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 572 * they will generally pass null for the callback and instead call 573 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 574 * which will then block until the request completes. 575 * <p> 576 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 577 * 578 * @param account The account whose credentials are to be updated. 579 * @param authTokenType the auth token to retrieve as part of updating the credentials. 580 * May be null. 581 * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the 582 * result then a "sign-on needed" notification will be created that will launch this intent. 583 * @param callback A callback to invoke when the request completes. If null then 584 * no callback is invoked. 585 * @param handler The {@link Handler} to use to invoke the callback. If null then the 586 * main thread's {@link Handler} is used. 587 * @return an {@link AccountManagerFuture} that represents the future result of the call. 588 * The future result is a {@link Bundle} that contains either: 589 * <ul> 590 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 591 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} 592 * if the authenticator is able to retrieve the auth token 593 * </ul> 594 * If the user presses "back" then the request will be canceled. 595 */ 596 public AccountManagerFuture<Bundle> getAuthToken( 597 final Account account, final String authTokenType, final boolean notifyAuthFailure, 598 AccountManagerCallback<Bundle> callback, Handler handler) { 599 if (account == null) throw new IllegalArgumentException("account is null"); 600 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 601 return new AmsTask(null, handler, callback) { 602 public void doWork() throws RemoteException { 603 mService.getAuthToken(mResponse, account, authTokenType, 604 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 605 } 606 }.start(); 607 } 608 609 /** 610 * Request that an account be added with the given accountType. This request 611 * is processed by the authenticator for the account type. If no authenticator is registered 612 * in the system then {@link AuthenticatorException} is thrown. 613 * <p> 614 * This call returns immediately but runs asynchronously and the result is accessed via the 615 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 616 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 617 * method asynchronously then they will generally pass in a callback object that will get 618 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 619 * they will generally pass null for the callback and instead call 620 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 621 * which will then block until the request completes. 622 * <p> 623 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 624 * 625 * @param accountType The type of account to add. This must not be null. 626 * @param authTokenType The account that is added should be able to service this auth token 627 * type. This may be null. 628 * @param requiredFeatures The account that is added should support these features. 629 * This array may be null or empty. 630 * @param addAccountOptions A bundle of authenticator-specific options that is passed on 631 * to the authenticator. This may be null. 632 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 633 * the intent will be started with this activity. If activity is null then the result will 634 * be returned as-is. 635 * @param callback A callback to invoke when the request completes. If null then 636 * no callback is invoked. 637 * @param handler The {@link Handler} to use to invoke the callback. If null then the 638 * main thread's {@link Handler} is used. 639 * @return an {@link AccountManagerFuture} that represents the future result of the call. 640 * The future result is a {@link Bundle} that contains either: 641 * <ul> 642 * <li> {@link #KEY_INTENT}, or 643 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} 644 * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified). 645 * </ul> 646 */ 647 public AccountManagerFuture<Bundle> addAccount(final String accountType, 648 final String authTokenType, final String[] requiredFeatures, 649 final Bundle addAccountOptions, 650 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 651 return new AmsTask(activity, handler, callback) { 652 public void doWork() throws RemoteException { 653 if (accountType == null) { 654 Log.e(TAG, "the account must not be null"); 655 // to unblock caller waiting on Future.get() 656 set(new Bundle()); 657 return; 658 } 659 mService.addAcount(mResponse, accountType, authTokenType, 660 requiredFeatures, activity != null, addAccountOptions); 661 } 662 }.start(); 663 } 664 665 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 666 final String type, final String[] features, 667 AccountManagerCallback<Account[]> callback, Handler handler) { 668 return new Future2Task<Account[]>(handler, callback) { 669 public void doWork() throws RemoteException { 670 if (type == null) { 671 Log.e(TAG, "Type is null"); 672 set(new Account[0]); 673 return; 674 } 675 mService.getAccountsByFeatures(mResponse, type, features); 676 } 677 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 678 if (!bundle.containsKey(KEY_ACCOUNTS)) { 679 throw new AuthenticatorException("no result in response"); 680 } 681 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); 682 Account[] descs = new Account[parcelables.length]; 683 for (int i = 0; i < parcelables.length; i++) { 684 descs[i] = (Account) parcelables[i]; 685 } 686 return descs; 687 } 688 }.start(); 689 } 690 691 /** 692 * Requests that the authenticator checks that the user knows the credentials for the account. 693 * This is typically done by returning an intent to an activity that prompts the user to 694 * enter the credentials. This request 695 * is processed by the authenticator for the account. If no matching authenticator is 696 * registered in the system then {@link AuthenticatorException} is thrown. 697 * <p> 698 * This call returns immediately but runs asynchronously and the result is accessed via the 699 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 700 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 701 * method asynchronously then they will generally pass in a callback object that will get 702 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 703 * they will generally pass null for the callback and instead call 704 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 705 * which will then block until the request completes. 706 * <p> 707 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 708 * 709 * @param account The account whose credentials are to be checked 710 * @param options authenticator specific options for the request 711 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 712 * the intent will be started with this activity. If activity is null then the result will 713 * be returned as-is. 714 * @param callback A callback to invoke when the request completes. If null then 715 * no callback is invoked. 716 * @param handler The {@link Handler} to use to invoke the callback. If null then the 717 * main thread's {@link Handler} is used. 718 * @return an {@link AccountManagerFuture} that represents the future result of the call. 719 * The future result is a {@link Bundle} that contains either: 720 * <ul> 721 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 722 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 723 * credentials 724 * </ul> 725 * If the user presses "back" then the request will be canceled. 726 */ 727 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, 728 final Bundle options, 729 final Activity activity, 730 final AccountManagerCallback<Bundle> callback, 731 final Handler handler) { 732 return new AmsTask(activity, handler, callback) { 733 public void doWork() throws RemoteException { 734 mService.confirmCredentials(mResponse, account, options, activity != null); 735 } 736 }.start(); 737 } 738 739 /** 740 * Requests that the authenticator update the the credentials for a user. This is typically 741 * done by returning an intent to an activity that will prompt the user to update the stored 742 * credentials for the account. This request 743 * is processed by the authenticator for the account. If no matching authenticator is 744 * registered in the system then {@link AuthenticatorException} is thrown. 745 * <p> 746 * This call returns immediately but runs asynchronously and the result is accessed via the 747 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 748 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 749 * method asynchronously then they will generally pass in a callback object that will get 750 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 751 * they will generally pass null for the callback and instead call 752 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 753 * which will then block until the request completes. 754 * <p> 755 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 756 * 757 * @param account The account whose credentials are to be updated. 758 * @param authTokenType the auth token to retrieve as part of updating the credentials. 759 * May be null. 760 * @param options authenticator specific options for the request 761 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 762 * the intent will be started with this activity. If activity is null then the result will 763 * be returned as-is. 764 * @param callback A callback to invoke when the request completes. If null then 765 * no callback is invoked. 766 * @param handler The {@link Handler} to use to invoke the callback. If null then the 767 * main thread's {@link Handler} is used. 768 * @return an {@link AccountManagerFuture} that represents the future result of the call. 769 * The future result is a {@link Bundle} that contains either: 770 * <ul> 771 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 772 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 773 * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided. 774 * </ul> 775 * If the user presses "back" then the request will be canceled. 776 */ 777 public AccountManagerFuture<Bundle> updateCredentials(final Account account, 778 final String authTokenType, 779 final Bundle options, final Activity activity, 780 final AccountManagerCallback<Bundle> callback, 781 final Handler handler) { 782 return new AmsTask(activity, handler, callback) { 783 public void doWork() throws RemoteException { 784 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 785 options); 786 } 787 }.start(); 788 } 789 790 /** 791 * Request that the properties for an authenticator be updated. This is typically done by 792 * returning an intent to an activity that will allow the user to make changes. This request 793 * is processed by the authenticator for the account. If no matching authenticator is 794 * registered in the system then {@link AuthenticatorException} is thrown. 795 * <p> 796 * This call returns immediately but runs asynchronously and the result is accessed via the 797 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 798 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 799 * method asynchronously then they will generally pass in a callback object that will get 800 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 801 * they will generally pass null for the callback and instead call 802 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 803 * which will then block until the request completes. 804 * <p> 805 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 806 * 807 * @param accountType The account type of the authenticator whose properties are to be edited. 808 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 809 * the intent will be started with this activity. If activity is null then the result will 810 * be returned as-is. 811 * @param callback A callback to invoke when the request completes. If null then 812 * no callback is invoked. 813 * @param handler The {@link Handler} to use to invoke the callback. If null then the 814 * main thread's {@link Handler} is used. 815 * @return an {@link AccountManagerFuture} that represents the future result of the call. 816 * The future result is a {@link Bundle} that contains either: 817 * <ul> 818 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 819 * <li> nothing, returned if the edit completes successfully 820 * </ul> 821 * If the user presses "back" then the request will be canceled. 822 */ 823 public AccountManagerFuture<Bundle> editProperties(final String accountType, 824 final Activity activity, final AccountManagerCallback<Bundle> callback, 825 final Handler handler) { 826 return new AmsTask(activity, handler, callback) { 827 public void doWork() throws RemoteException { 828 mService.editProperties(mResponse, accountType, activity != null); 829 } 830 }.start(); 831 } 832 833 private void ensureNotOnMainThread() { 834 final Looper looper = Looper.myLooper(); 835 if (looper != null && looper == mContext.getMainLooper()) { 836 // We really want to throw an exception here, but GTalkService exercises this 837 // path quite a bit and needs some serious rewrite in order to work properly. 838 //noinspection ThrowableInstanceNeverThrow 839// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 840// new Exception()); 841 // TODO remove the log and throw this exception when the callers are fixed 842// throw new IllegalStateException( 843// "calling this from your main thread can lead to deadlock"); 844 } 845 } 846 847 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 848 final AccountManagerFuture<Bundle> future) { 849 handler = handler == null ? mMainHandler : handler; 850 handler.post(new Runnable() { 851 public void run() { 852 callback.run(future); 853 } 854 }); 855 } 856 857 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener, 858 final Account[] accounts) { 859 final Account[] accountsCopy = new Account[accounts.length]; 860 // send a copy to make sure that one doesn't 861 // change what another sees 862 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 863 handler = (handler == null) ? mMainHandler : handler; 864 handler.post(new Runnable() { 865 public void run() { 866 try { 867 listener.onAccountsUpdated(accountsCopy); 868 } catch (SQLException e) { 869 // Better luck next time. If the problem was disk-full, 870 // the STORAGE_OK intent will re-trigger the update. 871 Log.e(TAG, "Can't update accounts", e); 872 } 873 } 874 }); 875 } 876 877 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 878 final IAccountManagerResponse mResponse; 879 final Handler mHandler; 880 final AccountManagerCallback<Bundle> mCallback; 881 final Activity mActivity; 882 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 883 super(new Callable<Bundle>() { 884 public Bundle call() throws Exception { 885 throw new IllegalStateException("this should never be called"); 886 } 887 }); 888 889 mHandler = handler; 890 mCallback = callback; 891 mActivity = activity; 892 mResponse = new Response(); 893 } 894 895 public final AccountManagerFuture<Bundle> start() { 896 try { 897 doWork(); 898 } catch (RemoteException e) { 899 setException(e); 900 } 901 return this; 902 } 903 904 public abstract void doWork() throws RemoteException; 905 906 private Bundle internalGetResult(Long timeout, TimeUnit unit) 907 throws OperationCanceledException, IOException, AuthenticatorException { 908 ensureNotOnMainThread(); 909 try { 910 if (timeout == null) { 911 return get(); 912 } else { 913 return get(timeout, unit); 914 } 915 } catch (CancellationException e) { 916 throw new OperationCanceledException(); 917 } catch (TimeoutException e) { 918 // fall through and cancel 919 } catch (InterruptedException e) { 920 // fall through and cancel 921 } catch (ExecutionException e) { 922 final Throwable cause = e.getCause(); 923 if (cause instanceof IOException) { 924 throw (IOException) cause; 925 } else if (cause instanceof UnsupportedOperationException) { 926 throw new AuthenticatorException(cause); 927 } else if (cause instanceof AuthenticatorException) { 928 throw (AuthenticatorException) cause; 929 } else if (cause instanceof RuntimeException) { 930 throw (RuntimeException) cause; 931 } else if (cause instanceof Error) { 932 throw (Error) cause; 933 } else { 934 throw new IllegalStateException(cause); 935 } 936 } finally { 937 cancel(true /* interrupt if running */); 938 } 939 throw new OperationCanceledException(); 940 } 941 942 public Bundle getResult() 943 throws OperationCanceledException, IOException, AuthenticatorException { 944 return internalGetResult(null, null); 945 } 946 947 public Bundle getResult(long timeout, TimeUnit unit) 948 throws OperationCanceledException, IOException, AuthenticatorException { 949 return internalGetResult(timeout, unit); 950 } 951 952 protected void done() { 953 if (mCallback != null) { 954 postToHandler(mHandler, mCallback, this); 955 } 956 } 957 958 /** Handles the responses from the AccountManager */ 959 private class Response extends IAccountManagerResponse.Stub { 960 public void onResult(Bundle bundle) { 961 Intent intent = bundle.getParcelable("intent"); 962 if (intent != null && mActivity != null) { 963 // since the user provided an Activity we will silently start intents 964 // that we see 965 mActivity.startActivity(intent); 966 // leave the Future running to wait for the real response to this request 967 } else if (bundle.getBoolean("retry")) { 968 try { 969 doWork(); 970 } catch (RemoteException e) { 971 // this will only happen if the system process is dead, which means 972 // we will be dying ourselves 973 } 974 } else { 975 set(bundle); 976 } 977 } 978 979 public void onError(int code, String message) { 980 if (code == ERROR_CODE_CANCELED) { 981 // the authenticator indicated that this request was canceled, do so now 982 cancel(true /* mayInterruptIfRunning */); 983 return; 984 } 985 setException(convertErrorToException(code, message)); 986 } 987 } 988 989 } 990 991 private abstract class BaseFutureTask<T> extends FutureTask<T> { 992 final public IAccountManagerResponse mResponse; 993 final Handler mHandler; 994 995 public BaseFutureTask(Handler handler) { 996 super(new Callable<T>() { 997 public T call() throws Exception { 998 throw new IllegalStateException("this should never be called"); 999 } 1000 }); 1001 mHandler = handler; 1002 mResponse = new Response(); 1003 } 1004 1005 public abstract void doWork() throws RemoteException; 1006 1007 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 1008 1009 protected void postRunnableToHandler(Runnable runnable) { 1010 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 1011 handler.post(runnable); 1012 } 1013 1014 protected void startTask() { 1015 try { 1016 doWork(); 1017 } catch (RemoteException e) { 1018 setException(e); 1019 } 1020 } 1021 1022 protected class Response extends IAccountManagerResponse.Stub { 1023 public void onResult(Bundle bundle) { 1024 try { 1025 T result = bundleToResult(bundle); 1026 if (result == null) { 1027 return; 1028 } 1029 set(result); 1030 return; 1031 } catch (ClassCastException e) { 1032 // we will set the exception below 1033 } catch (AuthenticatorException e) { 1034 // we will set the exception below 1035 } 1036 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response"); 1037 } 1038 1039 public void onError(int code, String message) { 1040 if (code == ERROR_CODE_CANCELED) { 1041 cancel(true /* mayInterruptIfRunning */); 1042 return; 1043 } 1044 setException(convertErrorToException(code, message)); 1045 } 1046 } 1047 } 1048 1049 private abstract class Future2Task<T> 1050 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 1051 final AccountManagerCallback<T> mCallback; 1052 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 1053 super(handler); 1054 mCallback = callback; 1055 } 1056 1057 protected void done() { 1058 if (mCallback != null) { 1059 postRunnableToHandler(new Runnable() { 1060 public void run() { 1061 mCallback.run(Future2Task.this); 1062 } 1063 }); 1064 } 1065 } 1066 1067 public Future2Task<T> start() { 1068 startTask(); 1069 return this; 1070 } 1071 1072 private T internalGetResult(Long timeout, TimeUnit unit) 1073 throws OperationCanceledException, IOException, AuthenticatorException { 1074 ensureNotOnMainThread(); 1075 try { 1076 if (timeout == null) { 1077 return get(); 1078 } else { 1079 return get(timeout, unit); 1080 } 1081 } catch (InterruptedException e) { 1082 // fall through and cancel 1083 } catch (TimeoutException e) { 1084 // fall through and cancel 1085 } catch (CancellationException e) { 1086 // fall through and cancel 1087 } catch (ExecutionException e) { 1088 final Throwable cause = e.getCause(); 1089 if (cause instanceof IOException) { 1090 throw (IOException) cause; 1091 } else if (cause instanceof UnsupportedOperationException) { 1092 throw new AuthenticatorException(cause); 1093 } else if (cause instanceof AuthenticatorException) { 1094 throw (AuthenticatorException) cause; 1095 } else if (cause instanceof RuntimeException) { 1096 throw (RuntimeException) cause; 1097 } else if (cause instanceof Error) { 1098 throw (Error) cause; 1099 } else { 1100 throw new IllegalStateException(cause); 1101 } 1102 } finally { 1103 cancel(true /* interrupt if running */); 1104 } 1105 throw new OperationCanceledException(); 1106 } 1107 1108 public T getResult() 1109 throws OperationCanceledException, IOException, AuthenticatorException { 1110 return internalGetResult(null, null); 1111 } 1112 1113 public T getResult(long timeout, TimeUnit unit) 1114 throws OperationCanceledException, IOException, AuthenticatorException { 1115 return internalGetResult(timeout, unit); 1116 } 1117 1118 } 1119 1120 private Exception convertErrorToException(int code, String message) { 1121 if (code == ERROR_CODE_NETWORK_ERROR) { 1122 return new IOException(message); 1123 } 1124 1125 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) { 1126 return new UnsupportedOperationException(message); 1127 } 1128 1129 if (code == ERROR_CODE_INVALID_RESPONSE) { 1130 return new AuthenticatorException(message); 1131 } 1132 1133 if (code == ERROR_CODE_BAD_ARGUMENTS) { 1134 return new IllegalArgumentException(message); 1135 } 1136 1137 return new AuthenticatorException(message); 1138 } 1139 1140 private class GetAuthTokenByTypeAndFeaturesTask 1141 extends AmsTask implements AccountManagerCallback<Bundle> { 1142 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 1143 final String[] features, Activity activityForPrompting, 1144 final Bundle addAccountOptions, final Bundle loginOptions, 1145 AccountManagerCallback<Bundle> callback, Handler handler) { 1146 super(activityForPrompting, handler, callback); 1147 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1148 mAccountType = accountType; 1149 mAuthTokenType = authTokenType; 1150 mFeatures = features; 1151 mAddAccountOptions = addAccountOptions; 1152 mLoginOptions = loginOptions; 1153 mMyCallback = this; 1154 } 1155 volatile AccountManagerFuture<Bundle> mFuture = null; 1156 final String mAccountType; 1157 final String mAuthTokenType; 1158 final String[] mFeatures; 1159 final Bundle mAddAccountOptions; 1160 final Bundle mLoginOptions; 1161 final AccountManagerCallback<Bundle> mMyCallback; 1162 1163 public void doWork() throws RemoteException { 1164 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 1165 new AccountManagerCallback<Account[]>() { 1166 public void run(AccountManagerFuture<Account[]> future) { 1167 Account[] accounts; 1168 try { 1169 accounts = future.getResult(); 1170 } catch (OperationCanceledException e) { 1171 setException(e); 1172 return; 1173 } catch (IOException e) { 1174 setException(e); 1175 return; 1176 } catch (AuthenticatorException e) { 1177 setException(e); 1178 return; 1179 } 1180 1181 if (accounts.length == 0) { 1182 if (mActivity != null) { 1183 // no accounts, add one now. pretend that the user directly 1184 // made this request 1185 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 1186 mAddAccountOptions, mActivity, mMyCallback, mHandler); 1187 } else { 1188 // send result since we can't prompt to add an account 1189 Bundle result = new Bundle(); 1190 result.putString(KEY_ACCOUNT_NAME, null); 1191 result.putString(KEY_ACCOUNT_TYPE, null); 1192 result.putString(KEY_AUTHTOKEN, null); 1193 try { 1194 mResponse.onResult(result); 1195 } catch (RemoteException e) { 1196 // this will never happen 1197 } 1198 // we are done 1199 } 1200 } else if (accounts.length == 1) { 1201 // have a single account, return an authtoken for it 1202 if (mActivity == null) { 1203 mFuture = getAuthToken(accounts[0], mAuthTokenType, 1204 false /* notifyAuthFailure */, mMyCallback, mHandler); 1205 } else { 1206 mFuture = getAuthToken(accounts[0], 1207 mAuthTokenType, mLoginOptions, 1208 mActivity, mMyCallback, mHandler); 1209 } 1210 } else { 1211 if (mActivity != null) { 1212 IAccountManagerResponse chooseResponse = 1213 new IAccountManagerResponse.Stub() { 1214 public void onResult(Bundle value) throws RemoteException { 1215 Account account = new Account( 1216 value.getString(KEY_ACCOUNT_NAME), 1217 value.getString(KEY_ACCOUNT_TYPE)); 1218 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 1219 mActivity, mMyCallback, mHandler); 1220 } 1221 1222 public void onError(int errorCode, String errorMessage) 1223 throws RemoteException { 1224 mResponse.onError(errorCode, errorMessage); 1225 } 1226 }; 1227 // have many accounts, launch the chooser 1228 Intent intent = new Intent(); 1229 intent.setClassName("android", 1230 "android.accounts.ChooseAccountActivity"); 1231 intent.putExtra(KEY_ACCOUNTS, accounts); 1232 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE, 1233 new AccountManagerResponse(chooseResponse)); 1234 mActivity.startActivity(intent); 1235 // the result will arrive via the IAccountManagerResponse 1236 } else { 1237 // send result since we can't prompt to select an account 1238 Bundle result = new Bundle(); 1239 result.putString(KEY_ACCOUNTS, null); 1240 try { 1241 mResponse.onResult(result); 1242 } catch (RemoteException e) { 1243 // this will never happen 1244 } 1245 // we are done 1246 } 1247 } 1248 }}, mHandler); 1249 } 1250 1251 public void run(AccountManagerFuture<Bundle> future) { 1252 try { 1253 set(future.getResult()); 1254 } catch (OperationCanceledException e) { 1255 cancel(true /* mayInterruptIfRUnning */); 1256 } catch (IOException e) { 1257 setException(e); 1258 } catch (AuthenticatorException e) { 1259 setException(e); 1260 } 1261 } 1262 } 1263 1264 /** 1265 * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures}, 1266 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)}, 1267 * and {@link #addAccount}. It first gets the list of accounts that match accountType and the 1268 * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType 1269 * feature set, and addAccountOptions. If there is exactly one then 1270 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is 1271 * called with that account. If there are more than one then a chooser activity is launched 1272 * to prompt the user to select one of them and then the authtoken is retrieved for it, 1273 * <p> 1274 * This call returns immediately but runs asynchronously and the result is accessed via the 1275 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 1276 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 1277 * method asynchronously then they will generally pass in a callback object that will get 1278 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 1279 * they will generally pass null for the callback and instead call 1280 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 1281 * which will then block until the request completes. 1282 * <p> 1283 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1284 * 1285 * @param accountType the accountType to query; this must be non-null 1286 * @param authTokenType the type of authtoken to retrieve; this must be non-null 1287 * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}. 1288 * @param activityForPrompting The activity used to start any account management 1289 * activities that are required to fulfill this request. This may be null. 1290 * @param addAccountOptions authenticator-specific options used if an account needs to be added 1291 * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken 1292 * @param callback A callback to invoke when the request completes. If null then 1293 * no callback is invoked. 1294 * @param handler The {@link Handler} to use to invoke the callback. If null then the 1295 * main thread's {@link Handler} is used. 1296 * @return an {@link AccountManagerFuture} that represents the future result of the call. 1297 * The future result is a {@link Bundle} that contains either: 1298 * <ul> 1299 * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to 1300 * fulfill the request. 1301 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the 1302 * request completes successfully. 1303 * </ul> 1304 * If the user presses "back" then the request will be canceled. 1305 */ 1306 public AccountManagerFuture<Bundle> getAuthTokenByFeatures( 1307 final String accountType, final String authTokenType, final String[] features, 1308 final Activity activityForPrompting, final Bundle addAccountOptions, 1309 final Bundle getAuthTokenOptions, 1310 final AccountManagerCallback<Bundle> callback, final Handler handler) { 1311 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1312 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1313 final GetAuthTokenByTypeAndFeaturesTask task = 1314 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 1315 activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler); 1316 task.start(); 1317 return task; 1318 } 1319 1320 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners = 1321 Maps.newHashMap(); 1322 1323 /** 1324 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 1325 * so that it can read the updated list of accounts and send them to the listener 1326 * in mAccountsUpdatedListeners. 1327 */ 1328 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 1329 public void onReceive(final Context context, final Intent intent) { 1330 final Account[] accounts = getAccounts(); 1331 // send the result to the listeners 1332 synchronized (mAccountsUpdatedListeners) { 1333 for (Map.Entry<OnAccountsUpdateListener, Handler> entry : 1334 mAccountsUpdatedListeners.entrySet()) { 1335 postToHandler(entry.getValue(), entry.getKey(), accounts); 1336 } 1337 } 1338 } 1339 }; 1340 1341 /** 1342 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. 1343 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 1344 * in or the main thread's Handler if handler is null. 1345 * <p> 1346 * You must remove this listener before the context that was used to retrieve this 1347 * {@link AccountManager} instance goes away. This generally means when the Activity 1348 * or Service you are running is stopped. 1349 * @param listener the listener to add 1350 * @param handler the Handler whose thread will be used to invoke the listener. If null 1351 * the AccountManager context's main thread will be used. 1352 * @param updateImmediately if true then the listener will be invoked as a result of this 1353 * call. 1354 * @throws IllegalArgumentException if listener is null 1355 * @throws IllegalStateException if listener was already added 1356 */ 1357 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, 1358 Handler handler, boolean updateImmediately) { 1359 if (listener == null) { 1360 throw new IllegalArgumentException("the listener is null"); 1361 } 1362 synchronized (mAccountsUpdatedListeners) { 1363 if (mAccountsUpdatedListeners.containsKey(listener)) { 1364 throw new IllegalStateException("this listener is already added"); 1365 } 1366 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1367 1368 mAccountsUpdatedListeners.put(listener, handler); 1369 1370 if (wasEmpty) { 1371 // Register a broadcast receiver to monitor account changes 1372 IntentFilter intentFilter = new IntentFilter(); 1373 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); 1374 // To recover from disk-full. 1375 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1376 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1377 } 1378 } 1379 1380 if (updateImmediately) { 1381 postToHandler(handler, listener, getAccounts()); 1382 } 1383 } 1384 1385 /** 1386 * Remove an {@link OnAccountsUpdateListener} that was previously registered with 1387 * {@link #addOnAccountsUpdatedListener}. 1388 * @param listener the listener to remove 1389 * @throws IllegalArgumentException if listener is null 1390 * @throws IllegalStateException if listener was not already added 1391 */ 1392 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) { 1393 if (listener == null) { 1394 Log.e(TAG, "Missing listener"); 1395 return; 1396 } 1397 synchronized (mAccountsUpdatedListeners) { 1398 if (!mAccountsUpdatedListeners.containsKey(listener)) { 1399 Log.e(TAG, "Listener was not previously added"); 1400 return; 1401 } 1402 mAccountsUpdatedListeners.remove(listener); 1403 if (mAccountsUpdatedListeners.isEmpty()) { 1404 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1405 } 1406 } 1407 } 1408} 1409