AccountManager.java revision 9788976b1465ce982b5ae7c741345edd0ecd9322
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.accounts; 18 19import android.app.Activity; 20import android.content.Intent; 21import android.content.Context; 22import android.content.IntentFilter; 23import android.content.BroadcastReceiver; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.RemoteException; 28import android.os.Parcelable; 29import android.util.Config; 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 blockingGetPassword(Account account) { 84 ensureNotOnMainThread(); 85 try { 86 return mService.getPassword(account); 87 } catch (RemoteException e) { 88 // if this happens the entire runtime will restart 89 throw new RuntimeException(e); 90 } 91 } 92 93 public Future1<String> getPassword(final Future1Callback<String> callback, 94 final Account account, final Handler handler) { 95 return startAsFuture(callback, handler, new Callable<String>() { 96 public String call() throws Exception { 97 return blockingGetPassword(account); 98 } 99 }); 100 } 101 102 public String blockingGetUserData(Account account, String key) { 103 ensureNotOnMainThread(); 104 try { 105 return mService.getUserData(account, key); 106 } catch (RemoteException e) { 107 // if this happens the entire runtime will restart 108 throw new RuntimeException(e); 109 } 110 } 111 112 public Future1<String> getUserData(Future1Callback<String> callback, 113 final Account account, final String key, Handler handler) { 114 return startAsFuture(callback, handler, new Callable<String>() { 115 public String call() throws Exception { 116 return blockingGetUserData(account, key); 117 } 118 }); 119 } 120 121 public AuthenticatorDescription[] blockingGetAuthenticatorTypes() { 122 ensureNotOnMainThread(); 123 try { 124 return mService.getAuthenticatorTypes(); 125 } catch (RemoteException e) { 126 // if this happens the entire runtime will restart 127 throw new RuntimeException(e); 128 } 129 } 130 131 public Future1<AuthenticatorDescription[]> getAuthenticatorTypes( 132 Future1Callback<AuthenticatorDescription[]> callback, Handler handler) { 133 return startAsFuture(callback, handler, new Callable<AuthenticatorDescription[]>() { 134 public AuthenticatorDescription[] call() throws Exception { 135 return blockingGetAuthenticatorTypes(); 136 } 137 }); 138 } 139 140 public Account[] blockingGetAccounts() { 141 ensureNotOnMainThread(); 142 try { 143 return mService.getAccounts(); 144 } catch (RemoteException e) { 145 // if this happens the entire runtime will restart 146 throw new RuntimeException(e); 147 } 148 } 149 150 public Account[] blockingGetAccountsByType(String accountType) { 151 ensureNotOnMainThread(); 152 try { 153 return mService.getAccountsByType(accountType); 154 } catch (RemoteException e) { 155 // if this happens the entire runtime will restart 156 throw new RuntimeException(e); 157 } 158 } 159 160 public Future1<Account[]> getAccounts(Future1Callback<Account[]> callback, Handler handler) { 161 return startAsFuture(callback, handler, new Callable<Account[]>() { 162 public Account[] call() throws Exception { 163 return blockingGetAccounts(); 164 } 165 }); 166 } 167 168 public Future1<Account[]> getAccountsByType(Future1Callback<Account[]> callback, 169 final String type, Handler handler) { 170 return startAsFuture(callback, handler, new Callable<Account[]>() { 171 public Account[] call() throws Exception { 172 return blockingGetAccountsByType(type); 173 } 174 }); 175 } 176 177 public boolean blockingAddAccountExplicitly(Account account, String password, Bundle extras) { 178 ensureNotOnMainThread(); 179 try { 180 return mService.addAccount(account, password, extras); 181 } catch (RemoteException e) { 182 // if this happens the entire runtime will restart 183 throw new RuntimeException(e); 184 } 185 } 186 187 public Future1<Boolean> addAccountExplicitly(final Future1Callback<Boolean> callback, 188 final Account account, final String password, final Bundle extras, 189 final Handler handler) { 190 return startAsFuture(callback, handler, new Callable<Boolean>() { 191 public Boolean call() throws Exception { 192 return blockingAddAccountExplicitly(account, password, extras); 193 } 194 }); 195 } 196 197 public void blockingRemoveAccount(Account account) { 198 ensureNotOnMainThread(); 199 try { 200 mService.removeAccount(account); 201 } catch (RemoteException e) { 202 // if this happens the entire runtime will restart 203 } 204 } 205 206 public Future1<Void> removeAccount(Future1Callback<Void> callback, final Account account, 207 final Handler handler) { 208 return startAsFuture(callback, handler, new Callable<Void>() { 209 public Void call() throws Exception { 210 blockingRemoveAccount(account); 211 return null; 212 } 213 }); 214 } 215 216 public void blockingInvalidateAuthToken(String accountType, String authToken) { 217 ensureNotOnMainThread(); 218 try { 219 mService.invalidateAuthToken(accountType, authToken); 220 } catch (RemoteException e) { 221 // if this happens the entire runtime will restart 222 } 223 } 224 225 public Future1<Void> invalidateAuthToken(Future1Callback<Void> callback, 226 final String accountType, final String authToken, final Handler handler) { 227 return startAsFuture(callback, handler, new Callable<Void>() { 228 public Void call() throws Exception { 229 blockingInvalidateAuthToken(accountType, authToken); 230 return null; 231 } 232 }); 233 } 234 235 public String blockingPeekAuthToken(Account account, String authTokenType) { 236 ensureNotOnMainThread(); 237 try { 238 return mService.peekAuthToken(account, authTokenType); 239 } catch (RemoteException e) { 240 // if this happens the entire runtime will restart 241 throw new RuntimeException(e); 242 } 243 } 244 245 public Future1<String> peekAuthToken(Future1Callback<String> callback, 246 final Account account, final String authTokenType, final Handler handler) { 247 return startAsFuture(callback, handler, new Callable<String>() { 248 public String call() throws Exception { 249 return blockingPeekAuthToken(account, authTokenType); 250 } 251 }); 252 } 253 254 public void blockingSetPassword(Account account, String password) { 255 ensureNotOnMainThread(); 256 try { 257 mService.setPassword(account, password); 258 } catch (RemoteException e) { 259 // if this happens the entire runtime will restart 260 } 261 } 262 263 public Future1<Void> setPassword(Future1Callback<Void> callback, 264 final Account account, final String password, final Handler handler) { 265 return startAsFuture(callback, handler, new Callable<Void>() { 266 public Void call() throws Exception { 267 blockingSetPassword(account, password); 268 return null; 269 } 270 }); 271 } 272 273 public void blockingClearPassword(Account account) { 274 ensureNotOnMainThread(); 275 try { 276 mService.clearPassword(account); 277 } catch (RemoteException e) { 278 // if this happens the entire runtime will restart 279 } 280 } 281 282 public Future1<Void> clearPassword(final Future1Callback<Void> callback, final Account account, 283 final Handler handler) { 284 return startAsFuture(callback, handler, new Callable<Void>() { 285 public Void call() throws Exception { 286 blockingClearPassword(account); 287 return null; 288 } 289 }); 290 } 291 292 public void blockingSetUserData(Account account, String key, String value) { 293 ensureNotOnMainThread(); 294 try { 295 mService.setUserData(account, key, value); 296 } catch (RemoteException e) { 297 // if this happens the entire runtime will restart 298 } 299 } 300 301 public Future1<Void> setUserData(Future1Callback<Void> callback, 302 final Account account, final String key, final String value, final Handler handler) { 303 return startAsFuture(callback, handler, new Callable<Void>() { 304 public Void call() throws Exception { 305 blockingSetUserData(account, key, value); 306 return null; 307 } 308 }); 309 } 310 311 public void blockingSetAuthToken(Account account, String authTokenType, String authToken) { 312 ensureNotOnMainThread(); 313 try { 314 mService.setAuthToken(account, authTokenType, authToken); 315 } catch (RemoteException e) { 316 // if this happens the entire runtime will restart 317 } 318 } 319 320 public Future1<Void> setAuthToken(Future1Callback<Void> callback, 321 final Account account, final String authTokenType, final String authToken, 322 final Handler handler) { 323 return startAsFuture(callback, handler, new Callable<Void>() { 324 public Void call() throws Exception { 325 blockingSetAuthToken(account, authTokenType, authToken); 326 return null; 327 } 328 }); 329 } 330 331 public String blockingGetAuthToken(Account account, String authTokenType, 332 boolean notifyAuthFailure) 333 throws OperationCanceledException, IOException, AuthenticatorException { 334 ensureNotOnMainThread(); 335 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 336 null /* handler */).getResult(); 337 return bundle.getString(Constants.AUTHTOKEN_KEY); 338 } 339 340 /** 341 * Request the auth token for this account/authTokenType. If this succeeds then the 342 * auth token will then be passed to the activity. If this results in an authentication 343 * failure then a login intent will be returned that can be invoked to prompt the user to 344 * update their credentials. This login activity will return the auth token to the calling 345 * activity. If activity is null then the login intent will not be invoked. 346 * 347 * @param account the account whose auth token should be retrieved 348 * @param authTokenType the auth token type that should be retrieved 349 * @param loginOptions 350 * @param activity the activity to launch the login intent, if necessary, and to which 351 */ 352 public Future2 getAuthToken( 353 final Account account, final String authTokenType, final Bundle loginOptions, 354 final Activity activity, Future2Callback callback, Handler handler) { 355 if (activity == null) throw new IllegalArgumentException("activity is null"); 356 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 357 return new AmsTask(activity, handler, callback) { 358 public void doWork() throws RemoteException { 359 mService.getAuthToken(mResponse, account, authTokenType, 360 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 361 loginOptions); 362 } 363 }.start(); 364 } 365 366 public Future2 getAuthToken( 367 final Account account, final String authTokenType, final boolean notifyAuthFailure, 368 Future2Callback callback, Handler handler) { 369 if (account == null) throw new IllegalArgumentException("account is null"); 370 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 371 return new AmsTask(null, handler, callback) { 372 public void doWork() throws RemoteException { 373 mService.getAuthToken(mResponse, account, authTokenType, 374 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 375 } 376 }.start(); 377 } 378 379 public Future2 addAccount(final String accountType, 380 final String authTokenType, final String[] requiredFeatures, 381 final Bundle addAccountOptions, 382 final Activity activity, Future2Callback callback, Handler handler) { 383 return new AmsTask(activity, handler, callback) { 384 public void doWork() throws RemoteException { 385 mService.addAcount(mResponse, accountType, authTokenType, 386 requiredFeatures, activity != null, addAccountOptions); 387 } 388 }.start(); 389 } 390 391 /** @deprecated use {@link #confirmCredentials} instead */ 392 public Future1<Boolean> confirmPassword(final Account account, final String password, 393 Future1Callback<Boolean> callback, Handler handler) { 394 return new AMSTaskBoolean(handler, callback) { 395 public void doWork() throws RemoteException { 396 mService.confirmPassword(response, account, password); 397 } 398 }; 399 } 400 401 public Account[] blockingGetAccountsWithTypeAndFeatures(String type, String[] features) 402 throws AuthenticatorException, IOException, OperationCanceledException { 403 Future2 future = getAccountsWithTypeAndFeatures(type, features, 404 null /* callback */, null /* handler */); 405 Bundle result = future.getResult(); 406 Parcelable[] accountsTemp = result.getParcelableArray(Constants.ACCOUNTS_KEY); 407 if (accountsTemp == null) { 408 throw new AuthenticatorException("accounts should not be null"); 409 } 410 Account[] accounts = new Account[accountsTemp.length]; 411 for (int i = 0; i < accountsTemp.length; i++) { 412 accounts[i] = (Account) accountsTemp[i]; 413 } 414 return accounts; 415 } 416 417 public Future2 getAccountsWithTypeAndFeatures( 418 final String type, final String[] features, 419 Future2Callback callback, Handler handler) { 420 if (type == null) throw new IllegalArgumentException("type is null"); 421 return new AmsTask(null /* activity */, handler, callback) { 422 public void doWork() throws RemoteException { 423 mService.getAccountsByTypeAndFeatures(mResponse, type, features); 424 } 425 }.start(); 426 } 427 428 public Future2 confirmCredentials(final Account account, final Activity activity, 429 final Future2Callback callback, 430 final Handler handler) { 431 return new AmsTask(activity, handler, callback) { 432 public void doWork() throws RemoteException { 433 mService.confirmCredentials(mResponse, account, activity != null); 434 } 435 }.start(); 436 } 437 438 public Future2 updateCredentials(final Account account, final String authTokenType, 439 final Bundle loginOptions, final Activity activity, 440 final Future2Callback callback, 441 final Handler handler) { 442 return new AmsTask(activity, handler, callback) { 443 public void doWork() throws RemoteException { 444 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 445 loginOptions); 446 } 447 }.start(); 448 } 449 450 public Future2 editProperties(final String accountType, final Activity activity, 451 final Future2Callback callback, 452 final Handler handler) { 453 return new AmsTask(activity, handler, callback) { 454 public void doWork() throws RemoteException { 455 mService.editProperties(mResponse, accountType, activity != null); 456 } 457 }.start(); 458 } 459 460 private void ensureNotOnMainThread() { 461 final Looper looper = Looper.myLooper(); 462 if (looper != null && looper == mContext.getMainLooper()) { 463 // We really want to throw an exception here, but GTalkService exercises this 464 // path quite a bit and needs some serious rewrite in order to work properly. 465 //noinspection ThrowableInstanceNeverThrow 466// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 467// new Exception()); 468 // TODO(fredq) remove the log and throw this exception when the callers are fixed 469// throw new IllegalStateException( 470// "calling this from your main thread can lead to deadlock"); 471 } 472 } 473 474 private void postToHandler(Handler handler, final Future2Callback callback, 475 final Future2 future) { 476 handler = handler == null ? mMainHandler : handler; 477 handler.post(new Runnable() { 478 public void run() { 479 callback.run(future); 480 } 481 }); 482 } 483 484 private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener, 485 final Account[] accounts) { 486 handler = handler == null ? mMainHandler : handler; 487 handler.post(new Runnable() { 488 public void run() { 489 listener.onAccountsUpdated(accounts); 490 } 491 }); 492 } 493 494 private <V> void postToHandler(Handler handler, final Future1Callback<V> callback, 495 final Future1<V> future) { 496 handler = handler == null ? mMainHandler : handler; 497 handler.post(new Runnable() { 498 public void run() { 499 callback.run(future); 500 } 501 }); 502 } 503 504 private <V> Future1<V> startAsFuture(Future1Callback<V> callback, Handler handler, 505 Callable<V> callable) { 506 final FutureTaskWithCallback<V> task = 507 new FutureTaskWithCallback<V>(callback, callable, handler); 508 new Thread(task).start(); 509 return task; 510 } 511 512 private class FutureTaskWithCallback<V> extends FutureTask<V> implements Future1<V> { 513 final Future1Callback<V> mCallback; 514 final Handler mHandler; 515 516 public FutureTaskWithCallback(Future1Callback<V> callback, Callable<V> callable, 517 Handler handler) { 518 super(callable); 519 mCallback = callback; 520 mHandler = handler; 521 } 522 523 protected void done() { 524 if (mCallback != null) { 525 postToHandler(mHandler, mCallback, this); 526 } 527 } 528 529 public V internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException { 530 try { 531 if (timeout == null) { 532 return get(); 533 } else { 534 return get(timeout, unit); 535 } 536 } catch (InterruptedException e) { 537 // we will cancel the task below 538 } catch (CancellationException e) { 539 // we will cancel the task below 540 } catch (TimeoutException e) { 541 // we will cancel the task below 542 } catch (ExecutionException e) { 543 // this should never happen 544 throw new IllegalStateException(e.getCause()); 545 } finally { 546 cancel(true /* interruptIfRunning */); 547 } 548 throw new OperationCanceledException(); 549 } 550 551 public V getResult() throws OperationCanceledException { 552 return internalGetResult(null, null); 553 } 554 555 public V getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 556 return internalGetResult(null, null); 557 } 558 } 559 560 private abstract class AmsTask extends FutureTask<Bundle> implements Future2 { 561 final IAccountManagerResponse mResponse; 562 final Handler mHandler; 563 final Future2Callback mCallback; 564 final Activity mActivity; 565 final Thread mThread; 566 public AmsTask(Activity activity, Handler handler, Future2Callback callback) { 567 super(new Callable<Bundle>() { 568 public Bundle call() throws Exception { 569 throw new IllegalStateException("this should never be called"); 570 } 571 }); 572 573 mHandler = handler; 574 mCallback = callback; 575 mActivity = activity; 576 mResponse = new Response(); 577 mThread = new Thread(new Runnable() { 578 public void run() { 579 try { 580 doWork(); 581 } catch (RemoteException e) { 582 // never happens 583 } 584 } 585 }, "AmsTask"); 586 } 587 588 public final Future2 start() { 589 mThread.start(); 590 return this; 591 } 592 593 public abstract void doWork() throws RemoteException; 594 595 private Bundle internalGetResult(Long timeout, TimeUnit unit) 596 throws OperationCanceledException, IOException, AuthenticatorException { 597 try { 598 if (timeout == null) { 599 return get(); 600 } else { 601 return get(timeout, unit); 602 } 603 } catch (CancellationException e) { 604 throw new OperationCanceledException(); 605 } catch (TimeoutException e) { 606 // fall through and cancel 607 } catch (InterruptedException 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 Bundle getResult() 631 throws OperationCanceledException, IOException, AuthenticatorException { 632 return internalGetResult(null, null); 633 } 634 635 public Bundle getResult(long timeout, TimeUnit unit) 636 throws OperationCanceledException, IOException, AuthenticatorException { 637 return internalGetResult(timeout, unit); 638 } 639 640 protected void done() { 641 if (mCallback != null) { 642 postToHandler(mHandler, mCallback, this); 643 } 644 } 645 646 /** Handles the responses from the AccountManager */ 647 private class Response extends IAccountManagerResponse.Stub { 648 public void onResult(Bundle bundle) { 649 Intent intent = bundle.getParcelable("intent"); 650 if (intent != null && mActivity != null) { 651 // since the user provided an Activity we will silently start intents 652 // that we see 653 mActivity.startActivity(intent); 654 // leave the Future running to wait for the real response to this request 655 } else { 656 set(bundle); 657 } 658 } 659 660 public void onError(int code, String message) { 661 if (code == Constants.ERROR_CODE_CANCELED) { 662 // the authenticator indicated that this request was canceled, do so now 663 cancel(true /* mayInterruptIfRunning */); 664 return; 665 } 666 setException(convertErrorToException(code, message)); 667 } 668 } 669 670 } 671 672 private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> { 673 final IAccountManagerResponse response; 674 final Handler mHandler; 675 final Future1Callback<Boolean> mCallback; 676 public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) { 677 super(new Callable<Boolean>() { 678 public Boolean call() throws Exception { 679 throw new IllegalStateException("this should never be called"); 680 } 681 }); 682 683 mHandler = handler; 684 mCallback = callback; 685 response = new Response(); 686 687 new Thread(new Runnable() { 688 public void run() { 689 try { 690 doWork(); 691 } catch (RemoteException e) { 692 // never happens 693 } 694 } 695 }).start(); 696 } 697 698 public abstract void doWork() throws RemoteException; 699 700 701 protected void done() { 702 if (mCallback != null) { 703 postToHandler(mHandler, mCallback, this); 704 } 705 } 706 707 private Boolean internalGetResult(Long timeout, TimeUnit unit) { 708 try { 709 if (timeout == null) { 710 return get(); 711 } else { 712 return get(timeout, unit); 713 } 714 } catch (InterruptedException e) { 715 // fall through and cancel 716 } catch (TimeoutException e) { 717 // fall through and cancel 718 } catch (CancellationException e) { 719 return false; 720 } catch (ExecutionException e) { 721 final Throwable cause = e.getCause(); 722 if (cause instanceof IOException) { 723 return false; 724 } else if (cause instanceof UnsupportedOperationException) { 725 return false; 726 } else if (cause instanceof AuthenticatorException) { 727 return false; 728 } else if (cause instanceof RuntimeException) { 729 throw (RuntimeException) cause; 730 } else if (cause instanceof Error) { 731 throw (Error) cause; 732 } else { 733 throw new IllegalStateException(cause); 734 } 735 } finally { 736 cancel(true /* interrupt if running */); 737 } 738 return false; 739 } 740 741 public Boolean getResult() throws OperationCanceledException { 742 return internalGetResult(null, null); 743 } 744 745 public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 746 return internalGetResult(timeout, unit); 747 } 748 749 private class Response extends IAccountManagerResponse.Stub { 750 public void onResult(Bundle bundle) { 751 try { 752 if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 753 set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY)); 754 return; 755 } 756 } catch (ClassCastException e) { 757 // we will set the exception below 758 } 759 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 760 } 761 762 public void onError(int code, String message) { 763 if (code == Constants.ERROR_CODE_CANCELED) { 764 cancel(true /* mayInterruptIfRunning */); 765 return; 766 } 767 setException(convertErrorToException(code, message)); 768 } 769 } 770 771 } 772 773 private Exception convertErrorToException(int code, String message) { 774 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 775 return new IOException(message); 776 } 777 778 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 779 return new UnsupportedOperationException(message); 780 } 781 782 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 783 return new AuthenticatorException(message); 784 } 785 786 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) { 787 return new IllegalArgumentException(message); 788 } 789 790 return new AuthenticatorException(message); 791 } 792 793 private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback { 794 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 795 final String[] features, Activity activityForPrompting, 796 final Bundle addAccountOptions, final Bundle loginOptions, 797 Future2Callback callback, Handler handler) { 798 super(activityForPrompting, handler, callback); 799 if (accountType == null) throw new IllegalArgumentException("account type is null"); 800 mAccountType = accountType; 801 mAuthTokenType = authTokenType; 802 mFeatures = features; 803 mAddAccountOptions = addAccountOptions; 804 mLoginOptions = loginOptions; 805 mMyCallback = this; 806 } 807 volatile Future2 mFuture = null; 808 final String mAccountType; 809 final String mAuthTokenType; 810 final String[] mFeatures; 811 final Bundle mAddAccountOptions; 812 final Bundle mLoginOptions; 813 final Future2Callback mMyCallback; 814 815 public void doWork() throws RemoteException { 816 getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() { 817 public void run(Future2 future) { 818 Bundle getAccountsResult; 819 try { 820 getAccountsResult = future.getResult(); 821 } catch (OperationCanceledException e) { 822 setException(e); 823 return; 824 } catch (IOException e) { 825 setException(e); 826 return; 827 } catch (AuthenticatorException e) { 828 setException(e); 829 return; 830 } 831 832 Parcelable[] accounts = 833 getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY); 834 if (accounts.length == 0) { 835 if (mActivity != null) { 836 // no accounts, add one now. pretend that the user directly 837 // made this request 838 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 839 mAddAccountOptions, mActivity, mMyCallback, mHandler); 840 } else { 841 // send result since we can't prompt to add an account 842 Bundle result = new Bundle(); 843 result.putString(Constants.ACCOUNT_NAME_KEY, null); 844 result.putString(Constants.ACCOUNT_TYPE_KEY, null); 845 result.putString(Constants.AUTHTOKEN_KEY, null); 846 try { 847 mResponse.onResult(result); 848 } catch (RemoteException e) { 849 // this will never happen 850 } 851 // we are done 852 } 853 } else if (accounts.length == 1) { 854 // have a single account, return an authtoken for it 855 if (mActivity == null) { 856 mFuture = getAuthToken((Account) accounts[0], mAuthTokenType, 857 false /* notifyAuthFailure */, mMyCallback, mHandler); 858 } else { 859 mFuture = getAuthToken((Account) accounts[0], 860 mAuthTokenType, mLoginOptions, 861 mActivity, mMyCallback, mHandler); 862 } 863 } else { 864 if (mActivity != null) { 865 IAccountManagerResponse chooseResponse = 866 new IAccountManagerResponse.Stub() { 867 public void onResult(Bundle value) throws RemoteException { 868 Account account = new Account( 869 value.getString(Constants.ACCOUNT_NAME_KEY), 870 value.getString(Constants.ACCOUNT_TYPE_KEY)); 871 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 872 mActivity, mMyCallback, mHandler); 873 } 874 875 public void onError(int errorCode, String errorMessage) 876 throws RemoteException { 877 mResponse.onError(errorCode, errorMessage); 878 } 879 }; 880 // have many accounts, launch the chooser 881 Intent intent = new Intent(); 882 intent.setClassName("android", 883 "android.accounts.ChooseAccountActivity"); 884 intent.putExtra(Constants.ACCOUNTS_KEY, accounts); 885 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY, 886 new AccountManagerResponse(chooseResponse)); 887 mActivity.startActivity(intent); 888 // the result will arrive via the IAccountManagerResponse 889 } else { 890 // send result since we can't prompt to select an account 891 Bundle result = new Bundle(); 892 result.putString(Constants.ACCOUNTS_KEY, null); 893 try { 894 mResponse.onResult(result); 895 } catch (RemoteException e) { 896 // this will never happen 897 } 898 // we are done 899 } 900 } 901 }}, mHandler); 902 } 903 904 905 906 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying 907 // future that we create. We need to do things like have cancel cancel the mFuture, if set 908 // or to cause this to be canceled if mFuture isn't set. 909 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2. 910 911 public void run(Future2 future) { 912 try { 913 set(future.get()); 914 } catch (InterruptedException e) { 915 cancel(true); 916 } catch (CancellationException e) { 917 cancel(true); 918 } catch (ExecutionException e) { 919 setException(e.getCause()); 920 } 921 } 922 } 923 924 public void getAuthTokenByFeatures( 925 final String accountType, final String authTokenType, final String[] features, 926 final Activity activityForPrompting, final Bundle addAccountOptions, 927 final Bundle loginOptions, 928 final Future2Callback callback, final Handler handler) { 929 if (accountType == null) throw new IllegalArgumentException("account type is null"); 930 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 931 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 932 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start(); 933 } 934 935 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners = 936 Maps.newHashMap(); 937 938 // These variable are only used from the LOGIN_ACCOUNTS_CHANGED_ACTION BroadcastReceiver 939 // and its getAccounts() callback which are both invoked only on the main thread. As a 940 // result we don't need to protect against concurrent accesses and any changes are guaranteed 941 // to be visible when used. Basically, these two variables are thread-confined. 942 private Future1<Account[]> mAccountsLookupFuture = null; 943 private boolean mAccountLookupPending = false; 944 945 /** 946 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 947 * so that it can read the updated list of accounts and send them to the listener 948 * in mAccountsUpdatedListeners. 949 */ 950 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 951 public void onReceive(final Context context, final Intent intent) { 952 if (mAccountsLookupFuture != null) { 953 // an accounts lookup is already in progress, 954 // don't bother starting another request 955 mAccountLookupPending = true; 956 return; 957 } 958 // initiate a read of the accounts 959 mAccountsLookupFuture = getAccounts(new Future1Callback<Account[]>() { 960 public void run(Future1<Account[]> future) { 961 // clear the future so that future receives will try the lookup again 962 mAccountsLookupFuture = null; 963 964 // get the accounts array 965 Account[] accounts; 966 try { 967 accounts = future.getResult(); 968 } catch (OperationCanceledException e) { 969 // this should never happen, but if it does pretend we got another 970 // accounts changed broadcast 971 if (Config.LOGD) { 972 Log.d(TAG, "the accounts lookup for listener notifications was " 973 + "canceled, try again by simulating the receipt of " 974 + "a LOGIN_ACCOUNTS_CHANGED_ACTION broadcast"); 975 } 976 onReceive(context, intent); 977 return; 978 } 979 980 // send the result to the listeners 981 synchronized (mAccountsUpdatedListeners) { 982 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry : 983 mAccountsUpdatedListeners.entrySet()) { 984 Account[] accountsCopy = new Account[accounts.length]; 985 // send the listeners a copy to make sure that one doesn't 986 // change what another sees 987 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 988 postToHandler(entry.getValue(), entry.getKey(), accountsCopy); 989 } 990 } 991 992 // If mAccountLookupPending was set when the account lookup finished it 993 // means that we had previously ignored a LOGIN_ACCOUNTS_CHANGED_ACTION 994 // intent because a lookup was already in progress. Now that we are done 995 // with this lookup and notification pretend that another intent 996 // was received by calling onReceive() directly. 997 if (mAccountLookupPending) { 998 mAccountLookupPending = false; 999 onReceive(context, intent); 1000 return; 1001 } 1002 } 1003 }, mMainHandler); 1004 } 1005 }; 1006 1007 /** 1008 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}. 1009 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 1010 * in or the main thread's Handler if handler is null. 1011 * @param listener the listener to add 1012 * @param handler the Handler whose thread will be used to invoke the listener. If null 1013 * the AccountManager context's main thread will be used. 1014 * @param updateImmediately if true then the listener will be invoked as a result of this 1015 * call. 1016 * @throws IllegalArgumentException if listener is null 1017 * @throws IllegalStateException if listener was already added 1018 */ 1019 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener, 1020 Handler handler, boolean updateImmediately) { 1021 if (listener == null) { 1022 throw new IllegalArgumentException("the listener is null"); 1023 } 1024 synchronized (mAccountsUpdatedListeners) { 1025 if (mAccountsUpdatedListeners.containsKey(listener)) { 1026 throw new IllegalStateException("this listener is already added"); 1027 } 1028 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1029 1030 mAccountsUpdatedListeners.put(listener, handler); 1031 1032 if (wasEmpty) { 1033 // Register a broadcast receiver to monitor account changes 1034 IntentFilter intentFilter = new IntentFilter(); 1035 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); 1036 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1037 } 1038 } 1039 1040 if (updateImmediately) { 1041 getAccounts(new Future1Callback<Account[]>() { 1042 public void run(Future1<Account[]> future) { 1043 try { 1044 listener.onAccountsUpdated(future.getResult()); 1045 } catch (OperationCanceledException e) { 1046 // ignore 1047 } 1048 } 1049 }, handler); 1050 } 1051 } 1052 1053 /** 1054 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with 1055 * {@link #addOnAccountsUpdatedListener}. 1056 * @param listener the listener to remove 1057 * @throws IllegalArgumentException if listener is null 1058 * @throws IllegalStateException if listener was not already added 1059 */ 1060 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) { 1061 if (listener == null) { 1062 throw new IllegalArgumentException("the listener is null"); 1063 } 1064 synchronized (mAccountsUpdatedListeners) { 1065 if (mAccountsUpdatedListeners.remove(listener) == null) { 1066 throw new IllegalStateException("this listener was not previously added"); 1067 } 1068 if (mAccountsUpdatedListeners.isEmpty()) { 1069 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1070 } 1071 } 1072 } 1073} 1074