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