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