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