AccountManager.java revision ffd0cb04f97e62d286d185c520580d81a9c328b1
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 public AccountManagerFuture<Boolean> confirmPassword(final Account account, final String password, 265 AccountManagerCallback<Boolean> callback, Handler handler) { 266 return new Future2Task<Boolean>(handler, callback) { 267 public void doWork() throws RemoteException { 268 mService.confirmPassword(mResponse, account, password); 269 } 270 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 271 if (!bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 272 throw new AuthenticatorException("no result in response"); 273 } 274 return bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY); 275 } 276 }.start(); 277 } 278 279 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 280 final String type, final String[] features, 281 AccountManagerCallback<Account[]> callback, Handler handler) { 282 if (type == null) throw new IllegalArgumentException("type is null"); 283 return new Future2Task<Account[]>(handler, callback) { 284 public void doWork() throws RemoteException { 285 mService.getAccountsByFeatures(mResponse, type, features); 286 } 287 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 288 if (!bundle.containsKey(Constants.ACCOUNTS_KEY)) { 289 throw new AuthenticatorException("no result in response"); 290 } 291 final Parcelable[] parcelables = bundle.getParcelableArray(Constants.ACCOUNTS_KEY); 292 Account[] descs = new Account[parcelables.length]; 293 for (int i = 0; i < parcelables.length; i++) { 294 descs[i] = (Account) parcelables[i]; 295 } 296 return descs; 297 } 298 }.start(); 299 } 300 301 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Activity activity, 302 final AccountManagerCallback<Bundle> callback, 303 final Handler handler) { 304 return new AmsTask(activity, handler, callback) { 305 public void doWork() throws RemoteException { 306 mService.confirmCredentials(mResponse, account, activity != null); 307 } 308 }.start(); 309 } 310 311 public AccountManagerFuture<Bundle> updateCredentials(final Account account, final String authTokenType, 312 final Bundle loginOptions, final Activity activity, 313 final AccountManagerCallback<Bundle> callback, 314 final Handler handler) { 315 return new AmsTask(activity, handler, callback) { 316 public void doWork() throws RemoteException { 317 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 318 loginOptions); 319 } 320 }.start(); 321 } 322 323 public AccountManagerFuture<Bundle> editProperties(final String accountType, final Activity activity, 324 final AccountManagerCallback<Bundle> callback, 325 final Handler handler) { 326 return new AmsTask(activity, handler, callback) { 327 public void doWork() throws RemoteException { 328 mService.editProperties(mResponse, accountType, activity != null); 329 } 330 }.start(); 331 } 332 333 private void ensureNotOnMainThread() { 334 final Looper looper = Looper.myLooper(); 335 if (looper != null && looper == mContext.getMainLooper()) { 336 // We really want to throw an exception here, but GTalkService exercises this 337 // path quite a bit and needs some serious rewrite in order to work properly. 338 //noinspection ThrowableInstanceNeverThrow 339// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 340// new Exception()); 341 // TODO(fredq) remove the log and throw this exception when the callers are fixed 342// throw new IllegalStateException( 343// "calling this from your main thread can lead to deadlock"); 344 } 345 } 346 347 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 348 final AccountManagerFuture<Bundle> future) { 349 handler = handler == null ? mMainHandler : handler; 350 handler.post(new Runnable() { 351 public void run() { 352 callback.run(future); 353 } 354 }); 355 } 356 357 private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener, 358 final Account[] accounts) { 359 final Account[] accountsCopy = new Account[accounts.length]; 360 // send a copy to make sure that one doesn't 361 // change what another sees 362 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 363 handler = (handler == null) ? mMainHandler : handler; 364 handler.post(new Runnable() { 365 public void run() { 366 listener.onAccountsUpdated(accountsCopy); 367 } 368 }); 369 } 370 371 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 372 final IAccountManagerResponse mResponse; 373 final Handler mHandler; 374 final AccountManagerCallback<Bundle> mCallback; 375 final Activity mActivity; 376 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 377 super(new Callable<Bundle>() { 378 public Bundle call() throws Exception { 379 throw new IllegalStateException("this should never be called"); 380 } 381 }); 382 383 mHandler = handler; 384 mCallback = callback; 385 mActivity = activity; 386 mResponse = new Response(); 387 } 388 389 public final AccountManagerFuture<Bundle> start() { 390 try { 391 doWork(); 392 } catch (RemoteException e) { 393 setException(e); 394 } 395 return this; 396 } 397 398 public abstract void doWork() throws RemoteException; 399 400 private Bundle internalGetResult(Long timeout, TimeUnit unit) 401 throws OperationCanceledException, IOException, AuthenticatorException { 402 ensureNotOnMainThread(); 403 try { 404 if (timeout == null) { 405 return get(); 406 } else { 407 return get(timeout, unit); 408 } 409 } catch (CancellationException e) { 410 throw new OperationCanceledException(); 411 } catch (TimeoutException e) { 412 // fall through and cancel 413 } catch (InterruptedException e) { 414 // fall through and cancel 415 } catch (ExecutionException e) { 416 final Throwable cause = e.getCause(); 417 if (cause instanceof IOException) { 418 throw (IOException) cause; 419 } else if (cause instanceof UnsupportedOperationException) { 420 throw new AuthenticatorException(cause); 421 } else if (cause instanceof AuthenticatorException) { 422 throw (AuthenticatorException) cause; 423 } else if (cause instanceof RuntimeException) { 424 throw (RuntimeException) cause; 425 } else if (cause instanceof Error) { 426 throw (Error) cause; 427 } else { 428 throw new IllegalStateException(cause); 429 } 430 } finally { 431 cancel(true /* interrupt if running */); 432 } 433 throw new OperationCanceledException(); 434 } 435 436 public Bundle getResult() 437 throws OperationCanceledException, IOException, AuthenticatorException { 438 return internalGetResult(null, null); 439 } 440 441 public Bundle getResult(long timeout, TimeUnit unit) 442 throws OperationCanceledException, IOException, AuthenticatorException { 443 return internalGetResult(timeout, unit); 444 } 445 446 protected void done() { 447 if (mCallback != null) { 448 postToHandler(mHandler, mCallback, this); 449 } 450 } 451 452 /** Handles the responses from the AccountManager */ 453 private class Response extends IAccountManagerResponse.Stub { 454 public void onResult(Bundle bundle) { 455 Intent intent = bundle.getParcelable("intent"); 456 if (intent != null && mActivity != null) { 457 // since the user provided an Activity we will silently start intents 458 // that we see 459 mActivity.startActivity(intent); 460 // leave the Future running to wait for the real response to this request 461 } else if (bundle.getBoolean("retry")) { 462 try { 463 doWork(); 464 } catch (RemoteException e) { 465 // this will only happen if the system process is dead, which means 466 // we will be dying ourselves 467 } 468 } else { 469 set(bundle); 470 } 471 } 472 473 public void onError(int code, String message) { 474 if (code == Constants.ERROR_CODE_CANCELED) { 475 // the authenticator indicated that this request was canceled, do so now 476 cancel(true /* mayInterruptIfRunning */); 477 return; 478 } 479 setException(convertErrorToException(code, message)); 480 } 481 } 482 483 } 484 485 private abstract class BaseFutureTask<T> extends FutureTask<T> { 486 final public IAccountManagerResponse mResponse; 487 final Handler mHandler; 488 489 public BaseFutureTask(Handler handler) { 490 super(new Callable<T>() { 491 public T call() throws Exception { 492 throw new IllegalStateException("this should never be called"); 493 } 494 }); 495 mHandler = handler; 496 mResponse = new Response(); 497 } 498 499 public abstract void doWork() throws RemoteException; 500 501 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 502 503 protected void postRunnableToHandler(Runnable runnable) { 504 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 505 handler.post(runnable); 506 } 507 508 protected void startTask() { 509 try { 510 doWork(); 511 } catch (RemoteException e) { 512 setException(e); 513 } 514 } 515 516 protected class Response extends IAccountManagerResponse.Stub { 517 public void onResult(Bundle bundle) { 518 try { 519 T result = bundleToResult(bundle); 520 if (result == null) { 521 return; 522 } 523 set(result); 524 return; 525 } catch (ClassCastException e) { 526 // we will set the exception below 527 } catch (AuthenticatorException e) { 528 // we will set the exception below 529 } 530 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 531 } 532 533 public void onError(int code, String message) { 534 if (code == Constants.ERROR_CODE_CANCELED) { 535 cancel(true /* mayInterruptIfRunning */); 536 return; 537 } 538 setException(convertErrorToException(code, message)); 539 } 540 } 541 } 542 543 private abstract class Future2Task<T> 544 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 545 final AccountManagerCallback<T> mCallback; 546 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 547 super(handler); 548 mCallback = callback; 549 } 550 551 protected void done() { 552 if (mCallback != null) { 553 postRunnableToHandler(new Runnable() { 554 public void run() { 555 mCallback.run(Future2Task.this); 556 } 557 }); 558 } 559 } 560 561 public Future2Task<T> start() { 562 startTask(); 563 return this; 564 } 565 566 private T internalGetResult(Long timeout, TimeUnit unit) 567 throws OperationCanceledException, IOException, AuthenticatorException { 568 ensureNotOnMainThread(); 569 try { 570 if (timeout == null) { 571 return get(); 572 } else { 573 return get(timeout, unit); 574 } 575 } catch (InterruptedException e) { 576 // fall through and cancel 577 } catch (TimeoutException e) { 578 // fall through and cancel 579 } catch (CancellationException e) { 580 // fall through and cancel 581 } catch (ExecutionException e) { 582 final Throwable cause = e.getCause(); 583 if (cause instanceof IOException) { 584 throw (IOException) cause; 585 } else if (cause instanceof UnsupportedOperationException) { 586 throw new AuthenticatorException(cause); 587 } else if (cause instanceof AuthenticatorException) { 588 throw (AuthenticatorException) cause; 589 } else if (cause instanceof RuntimeException) { 590 throw (RuntimeException) cause; 591 } else if (cause instanceof Error) { 592 throw (Error) cause; 593 } else { 594 throw new IllegalStateException(cause); 595 } 596 } finally { 597 cancel(true /* interrupt if running */); 598 } 599 throw new OperationCanceledException(); 600 } 601 602 public T getResult() 603 throws OperationCanceledException, IOException, AuthenticatorException { 604 return internalGetResult(null, null); 605 } 606 607 public T getResult(long timeout, TimeUnit unit) 608 throws OperationCanceledException, IOException, AuthenticatorException { 609 return internalGetResult(timeout, unit); 610 } 611 612 } 613 614 private Exception convertErrorToException(int code, String message) { 615 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 616 return new IOException(message); 617 } 618 619 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 620 return new UnsupportedOperationException(message); 621 } 622 623 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 624 return new AuthenticatorException(message); 625 } 626 627 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) { 628 return new IllegalArgumentException(message); 629 } 630 631 return new AuthenticatorException(message); 632 } 633 634 private class GetAuthTokenByTypeAndFeaturesTask 635 extends AmsTask implements AccountManagerCallback<Bundle> { 636 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 637 final String[] features, Activity activityForPrompting, 638 final Bundle addAccountOptions, final Bundle loginOptions, 639 AccountManagerCallback<Bundle> callback, Handler handler) { 640 super(activityForPrompting, handler, callback); 641 if (accountType == null) throw new IllegalArgumentException("account type is null"); 642 mAccountType = accountType; 643 mAuthTokenType = authTokenType; 644 mFeatures = features; 645 mAddAccountOptions = addAccountOptions; 646 mLoginOptions = loginOptions; 647 mMyCallback = this; 648 } 649 volatile AccountManagerFuture<Bundle> mFuture = null; 650 final String mAccountType; 651 final String mAuthTokenType; 652 final String[] mFeatures; 653 final Bundle mAddAccountOptions; 654 final Bundle mLoginOptions; 655 final AccountManagerCallback<Bundle> mMyCallback; 656 657 public void doWork() throws RemoteException { 658 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 659 new AccountManagerCallback<Account[]>() { 660 public void run(AccountManagerFuture<Account[]> future) { 661 Account[] accounts; 662 try { 663 accounts = future.getResult(); 664 } catch (OperationCanceledException e) { 665 setException(e); 666 return; 667 } catch (IOException e) { 668 setException(e); 669 return; 670 } catch (AuthenticatorException e) { 671 setException(e); 672 return; 673 } 674 675 if (accounts.length == 0) { 676 if (mActivity != null) { 677 // no accounts, add one now. pretend that the user directly 678 // made this request 679 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 680 mAddAccountOptions, mActivity, mMyCallback, mHandler); 681 } else { 682 // send result since we can't prompt to add an account 683 Bundle result = new Bundle(); 684 result.putString(Constants.ACCOUNT_NAME_KEY, null); 685 result.putString(Constants.ACCOUNT_TYPE_KEY, null); 686 result.putString(Constants.AUTHTOKEN_KEY, null); 687 try { 688 mResponse.onResult(result); 689 } catch (RemoteException e) { 690 // this will never happen 691 } 692 // we are done 693 } 694 } else if (accounts.length == 1) { 695 // have a single account, return an authtoken for it 696 if (mActivity == null) { 697 mFuture = getAuthToken(accounts[0], mAuthTokenType, 698 false /* notifyAuthFailure */, mMyCallback, mHandler); 699 } else { 700 mFuture = getAuthToken(accounts[0], 701 mAuthTokenType, mLoginOptions, 702 mActivity, mMyCallback, mHandler); 703 } 704 } else { 705 if (mActivity != null) { 706 IAccountManagerResponse chooseResponse = 707 new IAccountManagerResponse.Stub() { 708 public void onResult(Bundle value) throws RemoteException { 709 Account account = new Account( 710 value.getString(Constants.ACCOUNT_NAME_KEY), 711 value.getString(Constants.ACCOUNT_TYPE_KEY)); 712 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 713 mActivity, mMyCallback, mHandler); 714 } 715 716 public void onError(int errorCode, String errorMessage) 717 throws RemoteException { 718 mResponse.onError(errorCode, errorMessage); 719 } 720 }; 721 // have many accounts, launch the chooser 722 Intent intent = new Intent(); 723 intent.setClassName("android", 724 "android.accounts.ChooseAccountActivity"); 725 intent.putExtra(Constants.ACCOUNTS_KEY, accounts); 726 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY, 727 new AccountManagerResponse(chooseResponse)); 728 mActivity.startActivity(intent); 729 // the result will arrive via the IAccountManagerResponse 730 } else { 731 // send result since we can't prompt to select an account 732 Bundle result = new Bundle(); 733 result.putString(Constants.ACCOUNTS_KEY, null); 734 try { 735 mResponse.onResult(result); 736 } catch (RemoteException e) { 737 // this will never happen 738 } 739 // we are done 740 } 741 } 742 }}, mHandler); 743 } 744 745 746 747 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying 748 // future that we create. We need to do things like have cancel cancel the mFuture, if set 749 // or to cause this to be canceled if mFuture isn't set. 750 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2. 751 752 public void run(AccountManagerFuture<Bundle> future) { 753 try { 754 set(future.get()); 755 } catch (InterruptedException e) { 756 cancel(true); 757 } catch (CancellationException e) { 758 cancel(true); 759 } catch (ExecutionException e) { 760 setException(e.getCause()); 761 } 762 } 763 } 764 765 public void getAuthTokenByFeatures( 766 final String accountType, final String authTokenType, final String[] features, 767 final Activity activityForPrompting, final Bundle addAccountOptions, 768 final Bundle loginOptions, 769 final AccountManagerCallback<Bundle> callback, final Handler handler) { 770 if (accountType == null) throw new IllegalArgumentException("account type is null"); 771 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 772 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 773 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start(); 774 } 775 776 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners = 777 Maps.newHashMap(); 778 779 /** 780 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 781 * so that it can read the updated list of accounts and send them to the listener 782 * in mAccountsUpdatedListeners. 783 */ 784 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 785 public void onReceive(final Context context, final Intent intent) { 786 final Account[] accounts = getAccounts(); 787 // send the result to the listeners 788 synchronized (mAccountsUpdatedListeners) { 789 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry : 790 mAccountsUpdatedListeners.entrySet()) { 791 postToHandler(entry.getValue(), entry.getKey(), accounts); 792 } 793 } 794 } 795 }; 796 797 /** 798 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}. 799 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 800 * in or the main thread's Handler if handler is null. 801 * @param listener the listener to add 802 * @param handler the Handler whose thread will be used to invoke the listener. If null 803 * the AccountManager context's main thread will be used. 804 * @param updateImmediately if true then the listener will be invoked as a result of this 805 * call. 806 * @throws IllegalArgumentException if listener is null 807 * @throws IllegalStateException if listener was already added 808 */ 809 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener, 810 Handler handler, boolean updateImmediately) { 811 if (listener == null) { 812 throw new IllegalArgumentException("the listener is null"); 813 } 814 synchronized (mAccountsUpdatedListeners) { 815 if (mAccountsUpdatedListeners.containsKey(listener)) { 816 throw new IllegalStateException("this listener is already added"); 817 } 818 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 819 820 mAccountsUpdatedListeners.put(listener, handler); 821 822 if (wasEmpty) { 823 // Register a broadcast receiver to monitor account changes 824 IntentFilter intentFilter = new IntentFilter(); 825 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); 826 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 827 } 828 } 829 830 if (updateImmediately) { 831 postToHandler(handler, listener, getAccounts()); 832 } 833 } 834 835 /** 836 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with 837 * {@link #addOnAccountsUpdatedListener}. 838 * @param listener the listener to remove 839 * @throws IllegalArgumentException if listener is null 840 * @throws IllegalStateException if listener was not already added 841 */ 842 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) { 843 if (listener == null) { 844 throw new IllegalArgumentException("the listener is null"); 845 } 846 synchronized (mAccountsUpdatedListeners) { 847 if (mAccountsUpdatedListeners.remove(listener) == null) { 848 throw new IllegalStateException("this listener was not previously added"); 849 } 850 if (mAccountsUpdatedListeners.isEmpty()) { 851 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 852 } 853 } 854 } 855} 856