AccountManager.java revision 4a51c20ce607c74914f90fd897f04080121ac13b
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.os.Bundle; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.RemoteException; 28import android.os.Parcelable; 29 30import java.io.IOException; 31import java.util.concurrent.Callable; 32import java.util.concurrent.CancellationException; 33import java.util.concurrent.ExecutionException; 34import java.util.concurrent.FutureTask; 35import java.util.concurrent.TimeoutException; 36import java.util.concurrent.TimeUnit; 37import java.util.HashMap; 38import java.util.Map; 39 40import com.google.android.collect.Maps; 41 42/** 43 * A class that helps with interactions with the AccountManagerService. It provides 44 * methods to allow for account, password, and authtoken management for all accounts on the 45 * device. Some of these calls are implemented with the help of the corresponding 46 * {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling: 47 * AccountManager accountManager = AccountManager.get(context); 48 * 49 * <p> 50 * TODO(fredq) this interface is still in flux 51 */ 52public class AccountManager { 53 private static final String TAG = "AccountManager"; 54 55 private final Context mContext; 56 private final IAccountManager mService; 57 private final Handler mMainHandler; 58 59 /** 60 * @hide 61 */ 62 public AccountManager(Context context, IAccountManager service) { 63 mContext = context; 64 mService = service; 65 mMainHandler = new Handler(mContext.getMainLooper()); 66 } 67 68 /** 69 * @hide used for testing only 70 */ 71 public AccountManager(Context context, IAccountManager service, Handler handler) { 72 mContext = context; 73 mService = service; 74 mMainHandler = handler; 75 } 76 77 public static AccountManager get(Context context) { 78 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 79 } 80 81 public String getPassword(final Account account) { 82 try { 83 return mService.getPassword(account); 84 } catch (RemoteException e) { 85 // will never happen 86 throw new RuntimeException(e); 87 } 88 } 89 90 public String getUserData(final Account account, final String key) { 91 try { 92 return mService.getUserData(account, key); 93 } catch (RemoteException e) { 94 // will never happen 95 throw new RuntimeException(e); 96 } 97 } 98 99 public AuthenticatorDescription[] getAuthenticatorTypes() { 100 try { 101 return mService.getAuthenticatorTypes(); 102 } catch (RemoteException e) { 103 // will never happen 104 throw new RuntimeException(e); 105 } 106 } 107 108 public Account[] getAccounts() { 109 try { 110 return mService.getAccounts(null); 111 } catch (RemoteException e) { 112 // won't ever happen 113 throw new RuntimeException(e); 114 } 115 } 116 117 public Account[] getAccountsByType(String type) { 118 try { 119 return mService.getAccounts(type); 120 } catch (RemoteException e) { 121 // won't ever happen 122 throw new RuntimeException(e); 123 } 124 } 125 126 public boolean addAccountExplicitly(Account account, String password, Bundle extras) { 127 try { 128 return mService.addAccount(account, password, extras); 129 } catch (RemoteException e) { 130 // won't ever happen 131 throw new RuntimeException(e); 132 } 133 } 134 135 public AccountManagerFuture<Boolean> removeAccount(final Account account, 136 AccountManagerCallback<Boolean> callback, Handler handler) { 137 return new Future2Task<Boolean>(handler, callback) { 138 public void doWork() throws RemoteException { 139 mService.removeAccount(mResponse, account); 140 } 141 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 142 if (!bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 143 throw new AuthenticatorException("no result in response"); 144 } 145 return bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY); 146 } 147 }.start(); 148 } 149 150 public void invalidateAuthToken(final String accountType, final String authToken) { 151 try { 152 mService.invalidateAuthToken(accountType, authToken); 153 } catch (RemoteException e) { 154 // won't ever happen 155 throw new RuntimeException(e); 156 } 157 } 158 159 public String peekAuthToken(final Account account, final String authTokenType) { 160 try { 161 return mService.peekAuthToken(account, authTokenType); 162 } catch (RemoteException e) { 163 // won't ever happen 164 throw new RuntimeException(e); 165 } 166 } 167 168 public void setPassword(final Account account, final String password) { 169 try { 170 mService.setPassword(account, password); 171 } catch (RemoteException e) { 172 // won't ever happen 173 throw new RuntimeException(e); 174 } 175 } 176 177 public void clearPassword(final Account account) { 178 try { 179 mService.clearPassword(account); 180 } catch (RemoteException e) { 181 // won't ever happen 182 throw new RuntimeException(e); 183 } 184 } 185 186 public void setUserData(final Account account, final String key, final String value) { 187 try { 188 mService.setUserData(account, key, value); 189 } catch (RemoteException e) { 190 // won't ever happen 191 throw new RuntimeException(e); 192 } 193 } 194 195 public void setAuthToken(Account account, final String authTokenType, final String authToken) { 196 try { 197 mService.setAuthToken(account, authTokenType, authToken); 198 } catch (RemoteException e) { 199 // won't ever happen 200 throw new RuntimeException(e); 201 } 202 } 203 204 public String blockingGetAuthToken(Account account, String authTokenType, 205 boolean notifyAuthFailure) 206 throws OperationCanceledException, IOException, AuthenticatorException { 207 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 208 null /* handler */).getResult(); 209 return bundle.getString(Constants.AUTHTOKEN_KEY); 210 } 211 212 /** 213 * Request the auth token for this account/authTokenType. If this succeeds then the 214 * auth token will then be passed to the activity. If this results in an authentication 215 * failure then a login intent will be returned that can be invoked to prompt the user to 216 * update their credentials. This login activity will return the auth token to the calling 217 * activity. If activity is null then the login intent will not be invoked. 218 * 219 * @param account the account whose auth token should be retrieved 220 * @param authTokenType the auth token type that should be retrieved 221 * @param loginOptions 222 * @param activity the activity to launch the login intent, if necessary, and to which 223 */ 224 public AccountManagerFuture<Bundle> getAuthToken( 225 final Account account, final String authTokenType, final Bundle loginOptions, 226 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 227 if (activity == null) throw new IllegalArgumentException("activity is null"); 228 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 229 return new AmsTask(activity, handler, callback) { 230 public void doWork() throws RemoteException { 231 mService.getAuthToken(mResponse, account, authTokenType, 232 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 233 loginOptions); 234 } 235 }.start(); 236 } 237 238 public AccountManagerFuture<Bundle> getAuthToken( 239 final Account account, final String authTokenType, final boolean notifyAuthFailure, 240 AccountManagerCallback<Bundle> callback, Handler handler) { 241 if (account == null) throw new IllegalArgumentException("account is null"); 242 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 243 return new AmsTask(null, handler, callback) { 244 public void doWork() throws RemoteException { 245 mService.getAuthToken(mResponse, account, authTokenType, 246 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 247 } 248 }.start(); 249 } 250 251 public AccountManagerFuture<Bundle> addAccount(final String accountType, 252 final String authTokenType, final String[] requiredFeatures, 253 final Bundle addAccountOptions, 254 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 255 return new AmsTask(activity, handler, callback) { 256 public void doWork() throws RemoteException { 257 mService.addAcount(mResponse, accountType, authTokenType, 258 requiredFeatures, activity != null, addAccountOptions); 259 } 260 }.start(); 261 } 262 263 /** @deprecated use {@link #confirmCredentials} instead */ 264 @Deprecated 265 public AccountManagerFuture<Boolean> confirmPassword(final Account account, final String password, 266 AccountManagerCallback<Boolean> callback, Handler handler) { 267 return new Future2Task<Boolean>(handler, callback) { 268 public void doWork() throws RemoteException { 269 mService.confirmPassword(mResponse, account, password); 270 } 271 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 272 if (!bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 273 throw new AuthenticatorException("no result in response"); 274 } 275 return bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY); 276 } 277 }.start(); 278 } 279 280 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 281 final String type, final String[] features, 282 AccountManagerCallback<Account[]> callback, Handler handler) { 283 if (type == null) throw new IllegalArgumentException("type is null"); 284 return new Future2Task<Account[]>(handler, callback) { 285 public void doWork() throws RemoteException { 286 mService.getAccountsByFeatures(mResponse, type, features); 287 } 288 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 289 if (!bundle.containsKey(Constants.ACCOUNTS_KEY)) { 290 throw new AuthenticatorException("no result in response"); 291 } 292 final Parcelable[] parcelables = bundle.getParcelableArray(Constants.ACCOUNTS_KEY); 293 Account[] descs = new Account[parcelables.length]; 294 for (int i = 0; i < parcelables.length; i++) { 295 descs[i] = (Account) parcelables[i]; 296 } 297 return descs; 298 } 299 }.start(); 300 } 301 302 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Activity activity, 303 final AccountManagerCallback<Bundle> callback, 304 final Handler handler) { 305 return new AmsTask(activity, handler, callback) { 306 public void doWork() throws RemoteException { 307 mService.confirmCredentials(mResponse, account, activity != null); 308 } 309 }.start(); 310 } 311 312 public AccountManagerFuture<Bundle> updateCredentials(final Account account, final String authTokenType, 313 final Bundle loginOptions, final Activity activity, 314 final AccountManagerCallback<Bundle> callback, 315 final Handler handler) { 316 return new AmsTask(activity, handler, callback) { 317 public void doWork() throws RemoteException { 318 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 319 loginOptions); 320 } 321 }.start(); 322 } 323 324 public AccountManagerFuture<Bundle> editProperties(final String accountType, final Activity activity, 325 final AccountManagerCallback<Bundle> callback, 326 final Handler handler) { 327 return new AmsTask(activity, handler, callback) { 328 public void doWork() throws RemoteException { 329 mService.editProperties(mResponse, accountType, activity != null); 330 } 331 }.start(); 332 } 333 334 private void ensureNotOnMainThread() { 335 final Looper looper = Looper.myLooper(); 336 if (looper != null && looper == mContext.getMainLooper()) { 337 // We really want to throw an exception here, but GTalkService exercises this 338 // path quite a bit and needs some serious rewrite in order to work properly. 339 //noinspection ThrowableInstanceNeverThrow 340// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 341// new Exception()); 342 // TODO(fredq) remove the log and throw this exception when the callers are fixed 343// throw new IllegalStateException( 344// "calling this from your main thread can lead to deadlock"); 345 } 346 } 347 348 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 349 final AccountManagerFuture<Bundle> future) { 350 handler = handler == null ? mMainHandler : handler; 351 handler.post(new Runnable() { 352 public void run() { 353 callback.run(future); 354 } 355 }); 356 } 357 358 private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener, 359 final Account[] accounts) { 360 final Account[] accountsCopy = new Account[accounts.length]; 361 // send a copy to make sure that one doesn't 362 // change what another sees 363 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 364 handler = (handler == null) ? mMainHandler : handler; 365 handler.post(new Runnable() { 366 public void run() { 367 listener.onAccountsUpdated(accountsCopy); 368 } 369 }); 370 } 371 372 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 373 final IAccountManagerResponse mResponse; 374 final Handler mHandler; 375 final AccountManagerCallback<Bundle> mCallback; 376 final Activity mActivity; 377 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 378 super(new Callable<Bundle>() { 379 public Bundle call() throws Exception { 380 throw new IllegalStateException("this should never be called"); 381 } 382 }); 383 384 mHandler = handler; 385 mCallback = callback; 386 mActivity = activity; 387 mResponse = new Response(); 388 } 389 390 public final AccountManagerFuture<Bundle> start() { 391 try { 392 doWork(); 393 } catch (RemoteException e) { 394 setException(e); 395 } 396 return this; 397 } 398 399 public abstract void doWork() throws RemoteException; 400 401 private Bundle internalGetResult(Long timeout, TimeUnit unit) 402 throws OperationCanceledException, IOException, AuthenticatorException { 403 ensureNotOnMainThread(); 404 try { 405 if (timeout == null) { 406 return get(); 407 } else { 408 return get(timeout, unit); 409 } 410 } catch (CancellationException e) { 411 throw new OperationCanceledException(); 412 } catch (TimeoutException e) { 413 // fall through and cancel 414 } catch (InterruptedException e) { 415 // fall through and cancel 416 } catch (ExecutionException e) { 417 final Throwable cause = e.getCause(); 418 if (cause instanceof IOException) { 419 throw (IOException) cause; 420 } else if (cause instanceof UnsupportedOperationException) { 421 throw new AuthenticatorException(cause); 422 } else if (cause instanceof AuthenticatorException) { 423 throw (AuthenticatorException) cause; 424 } else if (cause instanceof RuntimeException) { 425 throw (RuntimeException) cause; 426 } else if (cause instanceof Error) { 427 throw (Error) cause; 428 } else { 429 throw new IllegalStateException(cause); 430 } 431 } finally { 432 cancel(true /* interrupt if running */); 433 } 434 throw new OperationCanceledException(); 435 } 436 437 public Bundle getResult() 438 throws OperationCanceledException, IOException, AuthenticatorException { 439 return internalGetResult(null, null); 440 } 441 442 public Bundle getResult(long timeout, TimeUnit unit) 443 throws OperationCanceledException, IOException, AuthenticatorException { 444 return internalGetResult(timeout, unit); 445 } 446 447 protected void done() { 448 if (mCallback != null) { 449 postToHandler(mHandler, mCallback, this); 450 } 451 } 452 453 /** Handles the responses from the AccountManager */ 454 private class Response extends IAccountManagerResponse.Stub { 455 public void onResult(Bundle bundle) { 456 Intent intent = bundle.getParcelable("intent"); 457 if (intent != null && mActivity != null) { 458 // since the user provided an Activity we will silently start intents 459 // that we see 460 mActivity.startActivity(intent); 461 // leave the Future running to wait for the real response to this request 462 } else if (bundle.getBoolean("retry")) { 463 try { 464 doWork(); 465 } catch (RemoteException e) { 466 // this will only happen if the system process is dead, which means 467 // we will be dying ourselves 468 } 469 } else { 470 set(bundle); 471 } 472 } 473 474 public void onError(int code, String message) { 475 if (code == Constants.ERROR_CODE_CANCELED) { 476 // the authenticator indicated that this request was canceled, do so now 477 cancel(true /* mayInterruptIfRunning */); 478 return; 479 } 480 setException(convertErrorToException(code, message)); 481 } 482 } 483 484 } 485 486 private abstract class BaseFutureTask<T> extends FutureTask<T> { 487 final public IAccountManagerResponse mResponse; 488 final Handler mHandler; 489 490 public BaseFutureTask(Handler handler) { 491 super(new Callable<T>() { 492 public T call() throws Exception { 493 throw new IllegalStateException("this should never be called"); 494 } 495 }); 496 mHandler = handler; 497 mResponse = new Response(); 498 } 499 500 public abstract void doWork() throws RemoteException; 501 502 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 503 504 protected void postRunnableToHandler(Runnable runnable) { 505 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 506 handler.post(runnable); 507 } 508 509 protected void startTask() { 510 try { 511 doWork(); 512 } catch (RemoteException e) { 513 setException(e); 514 } 515 } 516 517 protected class Response extends IAccountManagerResponse.Stub { 518 public void onResult(Bundle bundle) { 519 try { 520 T result = bundleToResult(bundle); 521 if (result == null) { 522 return; 523 } 524 set(result); 525 return; 526 } catch (ClassCastException e) { 527 // we will set the exception below 528 } catch (AuthenticatorException e) { 529 // we will set the exception below 530 } 531 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 532 } 533 534 public void onError(int code, String message) { 535 if (code == Constants.ERROR_CODE_CANCELED) { 536 cancel(true /* mayInterruptIfRunning */); 537 return; 538 } 539 setException(convertErrorToException(code, message)); 540 } 541 } 542 } 543 544 private abstract class Future2Task<T> 545 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 546 final AccountManagerCallback<T> mCallback; 547 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 548 super(handler); 549 mCallback = callback; 550 } 551 552 protected void done() { 553 if (mCallback != null) { 554 postRunnableToHandler(new Runnable() { 555 public void run() { 556 mCallback.run(Future2Task.this); 557 } 558 }); 559 } 560 } 561 562 public Future2Task<T> start() { 563 startTask(); 564 return this; 565 } 566 567 private T internalGetResult(Long timeout, TimeUnit unit) 568 throws OperationCanceledException, IOException, AuthenticatorException { 569 ensureNotOnMainThread(); 570 try { 571 if (timeout == null) { 572 return get(); 573 } else { 574 return get(timeout, unit); 575 } 576 } catch (InterruptedException e) { 577 // fall through and cancel 578 } catch (TimeoutException e) { 579 // fall through and cancel 580 } catch (CancellationException e) { 581 // fall through and cancel 582 } catch (ExecutionException e) { 583 final Throwable cause = e.getCause(); 584 if (cause instanceof IOException) { 585 throw (IOException) cause; 586 } else if (cause instanceof UnsupportedOperationException) { 587 throw new AuthenticatorException(cause); 588 } else if (cause instanceof AuthenticatorException) { 589 throw (AuthenticatorException) cause; 590 } else if (cause instanceof RuntimeException) { 591 throw (RuntimeException) cause; 592 } else if (cause instanceof Error) { 593 throw (Error) cause; 594 } else { 595 throw new IllegalStateException(cause); 596 } 597 } finally { 598 cancel(true /* interrupt if running */); 599 } 600 throw new OperationCanceledException(); 601 } 602 603 public T getResult() 604 throws OperationCanceledException, IOException, AuthenticatorException { 605 return internalGetResult(null, null); 606 } 607 608 public T getResult(long timeout, TimeUnit unit) 609 throws OperationCanceledException, IOException, AuthenticatorException { 610 return internalGetResult(timeout, unit); 611 } 612 613 } 614 615 private Exception convertErrorToException(int code, String message) { 616 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 617 return new IOException(message); 618 } 619 620 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 621 return new UnsupportedOperationException(message); 622 } 623 624 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 625 return new AuthenticatorException(message); 626 } 627 628 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) { 629 return new IllegalArgumentException(message); 630 } 631 632 return new AuthenticatorException(message); 633 } 634 635 private class GetAuthTokenByTypeAndFeaturesTask 636 extends AmsTask implements AccountManagerCallback<Bundle> { 637 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 638 final String[] features, Activity activityForPrompting, 639 final Bundle addAccountOptions, final Bundle loginOptions, 640 AccountManagerCallback<Bundle> callback, Handler handler) { 641 super(activityForPrompting, handler, callback); 642 if (accountType == null) throw new IllegalArgumentException("account type is null"); 643 mAccountType = accountType; 644 mAuthTokenType = authTokenType; 645 mFeatures = features; 646 mAddAccountOptions = addAccountOptions; 647 mLoginOptions = loginOptions; 648 mMyCallback = this; 649 } 650 volatile AccountManagerFuture<Bundle> mFuture = null; 651 final String mAccountType; 652 final String mAuthTokenType; 653 final String[] mFeatures; 654 final Bundle mAddAccountOptions; 655 final Bundle mLoginOptions; 656 final AccountManagerCallback<Bundle> mMyCallback; 657 658 public void doWork() throws RemoteException { 659 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 660 new AccountManagerCallback<Account[]>() { 661 public void run(AccountManagerFuture<Account[]> future) { 662 Account[] accounts; 663 try { 664 accounts = future.getResult(); 665 } catch (OperationCanceledException e) { 666 setException(e); 667 return; 668 } catch (IOException e) { 669 setException(e); 670 return; 671 } catch (AuthenticatorException e) { 672 setException(e); 673 return; 674 } 675 676 if (accounts.length == 0) { 677 if (mActivity != null) { 678 // no accounts, add one now. pretend that the user directly 679 // made this request 680 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 681 mAddAccountOptions, mActivity, mMyCallback, mHandler); 682 } else { 683 // send result since we can't prompt to add an account 684 Bundle result = new Bundle(); 685 result.putString(Constants.ACCOUNT_NAME_KEY, null); 686 result.putString(Constants.ACCOUNT_TYPE_KEY, null); 687 result.putString(Constants.AUTHTOKEN_KEY, null); 688 try { 689 mResponse.onResult(result); 690 } catch (RemoteException e) { 691 // this will never happen 692 } 693 // we are done 694 } 695 } else if (accounts.length == 1) { 696 // have a single account, return an authtoken for it 697 if (mActivity == null) { 698 mFuture = getAuthToken(accounts[0], mAuthTokenType, 699 false /* notifyAuthFailure */, mMyCallback, mHandler); 700 } else { 701 mFuture = getAuthToken(accounts[0], 702 mAuthTokenType, mLoginOptions, 703 mActivity, mMyCallback, mHandler); 704 } 705 } else { 706 if (mActivity != null) { 707 IAccountManagerResponse chooseResponse = 708 new IAccountManagerResponse.Stub() { 709 public void onResult(Bundle value) throws RemoteException { 710 Account account = new Account( 711 value.getString(Constants.ACCOUNT_NAME_KEY), 712 value.getString(Constants.ACCOUNT_TYPE_KEY)); 713 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 714 mActivity, mMyCallback, mHandler); 715 } 716 717 public void onError(int errorCode, String errorMessage) 718 throws RemoteException { 719 mResponse.onError(errorCode, errorMessage); 720 } 721 }; 722 // have many accounts, launch the chooser 723 Intent intent = new Intent(); 724 intent.setClassName("android", 725 "android.accounts.ChooseAccountActivity"); 726 intent.putExtra(Constants.ACCOUNTS_KEY, accounts); 727 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY, 728 new AccountManagerResponse(chooseResponse)); 729 mActivity.startActivity(intent); 730 // the result will arrive via the IAccountManagerResponse 731 } else { 732 // send result since we can't prompt to select an account 733 Bundle result = new Bundle(); 734 result.putString(Constants.ACCOUNTS_KEY, null); 735 try { 736 mResponse.onResult(result); 737 } catch (RemoteException e) { 738 // this will never happen 739 } 740 // we are done 741 } 742 } 743 }}, mHandler); 744 } 745 746 747 748 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying 749 // future that we create. We need to do things like have cancel cancel the mFuture, if set 750 // or to cause this to be canceled if mFuture isn't set. 751 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2. 752 753 public void run(AccountManagerFuture<Bundle> future) { 754 try { 755 set(future.get()); 756 } catch (InterruptedException e) { 757 cancel(true); 758 } catch (CancellationException e) { 759 cancel(true); 760 } catch (ExecutionException e) { 761 setException(e.getCause()); 762 } 763 } 764 } 765 766 public void getAuthTokenByFeatures( 767 final String accountType, final String authTokenType, final String[] features, 768 final Activity activityForPrompting, final Bundle addAccountOptions, 769 final Bundle loginOptions, 770 final AccountManagerCallback<Bundle> callback, final Handler handler) { 771 if (accountType == null) throw new IllegalArgumentException("account type is null"); 772 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 773 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 774 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start(); 775 } 776 777 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners = 778 Maps.newHashMap(); 779 780 /** 781 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 782 * so that it can read the updated list of accounts and send them to the listener 783 * in mAccountsUpdatedListeners. 784 */ 785 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 786 public void onReceive(final Context context, final Intent intent) { 787 final Account[] accounts = getAccounts(); 788 // send the result to the listeners 789 synchronized (mAccountsUpdatedListeners) { 790 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry : 791 mAccountsUpdatedListeners.entrySet()) { 792 postToHandler(entry.getValue(), entry.getKey(), accounts); 793 } 794 } 795 } 796 }; 797 798 /** 799 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}. 800 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 801 * in or the main thread's Handler if handler is null. 802 * @param listener the listener to add 803 * @param handler the Handler whose thread will be used to invoke the listener. If null 804 * the AccountManager context's main thread will be used. 805 * @param updateImmediately if true then the listener will be invoked as a result of this 806 * call. 807 * @throws IllegalArgumentException if listener is null 808 * @throws IllegalStateException if listener was already added 809 */ 810 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener, 811 Handler handler, boolean updateImmediately) { 812 if (listener == null) { 813 throw new IllegalArgumentException("the listener is null"); 814 } 815 synchronized (mAccountsUpdatedListeners) { 816 if (mAccountsUpdatedListeners.containsKey(listener)) { 817 throw new IllegalStateException("this listener is already added"); 818 } 819 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 820 821 mAccountsUpdatedListeners.put(listener, handler); 822 823 if (wasEmpty) { 824 // Register a broadcast receiver to monitor account changes 825 IntentFilter intentFilter = new IntentFilter(); 826 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); 827 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 828 } 829 } 830 831 if (updateImmediately) { 832 postToHandler(handler, listener, getAccounts()); 833 } 834 } 835 836 /** 837 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with 838 * {@link #addOnAccountsUpdatedListener}. 839 * @param listener the listener to remove 840 * @throws IllegalArgumentException if listener is null 841 * @throws IllegalStateException if listener was not already added 842 */ 843 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) { 844 if (listener == null) { 845 throw new IllegalArgumentException("the listener is null"); 846 } 847 synchronized (mAccountsUpdatedListeners) { 848 if (mAccountsUpdatedListeners.remove(listener) == null) { 849 throw new IllegalStateException("this listener was not previously added"); 850 } 851 if (mAccountsUpdatedListeners.isEmpty()) { 852 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 853 } 854 } 855 } 856} 857