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