AccountManager.java revision d4a1d2e14297a3387fdb5761090961e714370492
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 if (bundle.getBoolean("retry")) { 656 try { 657 doWork(); 658 } catch (RemoteException e) { 659 // this will only happen if the system process is dead, which means 660 // we will be dying ourselves 661 } 662 } else { 663 set(bundle); 664 } 665 } 666 667 public void onError(int code, String message) { 668 if (code == Constants.ERROR_CODE_CANCELED) { 669 // the authenticator indicated that this request was canceled, do so now 670 cancel(true /* mayInterruptIfRunning */); 671 return; 672 } 673 setException(convertErrorToException(code, message)); 674 } 675 } 676 677 } 678 679 private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> { 680 final IAccountManagerResponse response; 681 final Handler mHandler; 682 final Future1Callback<Boolean> mCallback; 683 public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) { 684 super(new Callable<Boolean>() { 685 public Boolean call() throws Exception { 686 throw new IllegalStateException("this should never be called"); 687 } 688 }); 689 690 mHandler = handler; 691 mCallback = callback; 692 response = new Response(); 693 694 new Thread(new Runnable() { 695 public void run() { 696 try { 697 doWork(); 698 } catch (RemoteException e) { 699 // never happens 700 } 701 } 702 }).start(); 703 } 704 705 public abstract void doWork() throws RemoteException; 706 707 708 protected void done() { 709 if (mCallback != null) { 710 postToHandler(mHandler, mCallback, this); 711 } 712 } 713 714 private Boolean internalGetResult(Long timeout, TimeUnit unit) { 715 try { 716 if (timeout == null) { 717 return get(); 718 } else { 719 return get(timeout, unit); 720 } 721 } catch (InterruptedException e) { 722 // fall through and cancel 723 } catch (TimeoutException e) { 724 // fall through and cancel 725 } catch (CancellationException e) { 726 return false; 727 } catch (ExecutionException e) { 728 final Throwable cause = e.getCause(); 729 if (cause instanceof IOException) { 730 return false; 731 } else if (cause instanceof UnsupportedOperationException) { 732 return false; 733 } else if (cause instanceof AuthenticatorException) { 734 return false; 735 } else if (cause instanceof RuntimeException) { 736 throw (RuntimeException) cause; 737 } else if (cause instanceof Error) { 738 throw (Error) cause; 739 } else { 740 throw new IllegalStateException(cause); 741 } 742 } finally { 743 cancel(true /* interrupt if running */); 744 } 745 return false; 746 } 747 748 public Boolean getResult() throws OperationCanceledException { 749 return internalGetResult(null, null); 750 } 751 752 public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 753 return internalGetResult(timeout, unit); 754 } 755 756 private class Response extends IAccountManagerResponse.Stub { 757 public void onResult(Bundle bundle) { 758 try { 759 if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 760 set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY)); 761 return; 762 } 763 } catch (ClassCastException e) { 764 // we will set the exception below 765 } 766 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 767 } 768 769 public void onError(int code, String message) { 770 if (code == Constants.ERROR_CODE_CANCELED) { 771 cancel(true /* mayInterruptIfRunning */); 772 return; 773 } 774 setException(convertErrorToException(code, message)); 775 } 776 } 777 778 } 779 780 private Exception convertErrorToException(int code, String message) { 781 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 782 return new IOException(message); 783 } 784 785 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 786 return new UnsupportedOperationException(message); 787 } 788 789 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 790 return new AuthenticatorException(message); 791 } 792 793 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) { 794 return new IllegalArgumentException(message); 795 } 796 797 return new AuthenticatorException(message); 798 } 799 800 private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback { 801 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 802 final String[] features, Activity activityForPrompting, 803 final Bundle addAccountOptions, final Bundle loginOptions, 804 Future2Callback callback, Handler handler) { 805 super(activityForPrompting, handler, callback); 806 if (accountType == null) throw new IllegalArgumentException("account type is null"); 807 mAccountType = accountType; 808 mAuthTokenType = authTokenType; 809 mFeatures = features; 810 mAddAccountOptions = addAccountOptions; 811 mLoginOptions = loginOptions; 812 mMyCallback = this; 813 } 814 volatile Future2 mFuture = null; 815 final String mAccountType; 816 final String mAuthTokenType; 817 final String[] mFeatures; 818 final Bundle mAddAccountOptions; 819 final Bundle mLoginOptions; 820 final Future2Callback mMyCallback; 821 822 public void doWork() throws RemoteException { 823 getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() { 824 public void run(Future2 future) { 825 Bundle getAccountsResult; 826 try { 827 getAccountsResult = future.getResult(); 828 } catch (OperationCanceledException e) { 829 setException(e); 830 return; 831 } catch (IOException e) { 832 setException(e); 833 return; 834 } catch (AuthenticatorException e) { 835 setException(e); 836 return; 837 } 838 839 Parcelable[] accounts = 840 getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY); 841 if (accounts.length == 0) { 842 if (mActivity != null) { 843 // no accounts, add one now. pretend that the user directly 844 // made this request 845 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 846 mAddAccountOptions, mActivity, mMyCallback, mHandler); 847 } else { 848 // send result since we can't prompt to add an account 849 Bundle result = new Bundle(); 850 result.putString(Constants.ACCOUNT_NAME_KEY, null); 851 result.putString(Constants.ACCOUNT_TYPE_KEY, null); 852 result.putString(Constants.AUTHTOKEN_KEY, null); 853 try { 854 mResponse.onResult(result); 855 } catch (RemoteException e) { 856 // this will never happen 857 } 858 // we are done 859 } 860 } else if (accounts.length == 1) { 861 // have a single account, return an authtoken for it 862 if (mActivity == null) { 863 mFuture = getAuthToken((Account) accounts[0], mAuthTokenType, 864 false /* notifyAuthFailure */, mMyCallback, mHandler); 865 } else { 866 mFuture = getAuthToken((Account) accounts[0], 867 mAuthTokenType, mLoginOptions, 868 mActivity, mMyCallback, mHandler); 869 } 870 } else { 871 if (mActivity != null) { 872 IAccountManagerResponse chooseResponse = 873 new IAccountManagerResponse.Stub() { 874 public void onResult(Bundle value) throws RemoteException { 875 Account account = new Account( 876 value.getString(Constants.ACCOUNT_NAME_KEY), 877 value.getString(Constants.ACCOUNT_TYPE_KEY)); 878 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 879 mActivity, mMyCallback, mHandler); 880 } 881 882 public void onError(int errorCode, String errorMessage) 883 throws RemoteException { 884 mResponse.onError(errorCode, errorMessage); 885 } 886 }; 887 // have many accounts, launch the chooser 888 Intent intent = new Intent(); 889 intent.setClassName("android", 890 "android.accounts.ChooseAccountActivity"); 891 intent.putExtra(Constants.ACCOUNTS_KEY, accounts); 892 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY, 893 new AccountManagerResponse(chooseResponse)); 894 mActivity.startActivity(intent); 895 // the result will arrive via the IAccountManagerResponse 896 } else { 897 // send result since we can't prompt to select an account 898 Bundle result = new Bundle(); 899 result.putString(Constants.ACCOUNTS_KEY, null); 900 try { 901 mResponse.onResult(result); 902 } catch (RemoteException e) { 903 // this will never happen 904 } 905 // we are done 906 } 907 } 908 }}, mHandler); 909 } 910 911 912 913 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying 914 // future that we create. We need to do things like have cancel cancel the mFuture, if set 915 // or to cause this to be canceled if mFuture isn't set. 916 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2. 917 918 public void run(Future2 future) { 919 try { 920 set(future.get()); 921 } catch (InterruptedException e) { 922 cancel(true); 923 } catch (CancellationException e) { 924 cancel(true); 925 } catch (ExecutionException e) { 926 setException(e.getCause()); 927 } 928 } 929 } 930 931 public void getAuthTokenByFeatures( 932 final String accountType, final String authTokenType, final String[] features, 933 final Activity activityForPrompting, final Bundle addAccountOptions, 934 final Bundle loginOptions, 935 final Future2Callback callback, final Handler handler) { 936 if (accountType == null) throw new IllegalArgumentException("account type is null"); 937 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 938 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 939 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start(); 940 } 941 942 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners = 943 Maps.newHashMap(); 944 945 // These variable are only used from the LOGIN_ACCOUNTS_CHANGED_ACTION BroadcastReceiver 946 // and its getAccounts() callback which are both invoked only on the main thread. As a 947 // result we don't need to protect against concurrent accesses and any changes are guaranteed 948 // to be visible when used. Basically, these two variables are thread-confined. 949 private Future1<Account[]> mAccountsLookupFuture = null; 950 private boolean mAccountLookupPending = false; 951 952 /** 953 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 954 * so that it can read the updated list of accounts and send them to the listener 955 * in mAccountsUpdatedListeners. 956 */ 957 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 958 public void onReceive(final Context context, final Intent intent) { 959 if (mAccountsLookupFuture != null) { 960 // an accounts lookup is already in progress, 961 // don't bother starting another request 962 mAccountLookupPending = true; 963 return; 964 } 965 // initiate a read of the accounts 966 mAccountsLookupFuture = getAccounts(new Future1Callback<Account[]>() { 967 public void run(Future1<Account[]> future) { 968 // clear the future so that future receives will try the lookup again 969 mAccountsLookupFuture = null; 970 971 // get the accounts array 972 Account[] accounts; 973 try { 974 accounts = future.getResult(); 975 } catch (OperationCanceledException e) { 976 // this should never happen, but if it does pretend we got another 977 // accounts changed broadcast 978 if (Config.LOGD) { 979 Log.d(TAG, "the accounts lookup for listener notifications was " 980 + "canceled, try again by simulating the receipt of " 981 + "a LOGIN_ACCOUNTS_CHANGED_ACTION broadcast"); 982 } 983 onReceive(context, intent); 984 return; 985 } 986 987 // send the result to the listeners 988 synchronized (mAccountsUpdatedListeners) { 989 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry : 990 mAccountsUpdatedListeners.entrySet()) { 991 Account[] accountsCopy = new Account[accounts.length]; 992 // send the listeners a copy to make sure that one doesn't 993 // change what another sees 994 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 995 postToHandler(entry.getValue(), entry.getKey(), accountsCopy); 996 } 997 } 998 999 // If mAccountLookupPending was set when the account lookup finished it 1000 // means that we had previously ignored a LOGIN_ACCOUNTS_CHANGED_ACTION 1001 // intent because a lookup was already in progress. Now that we are done 1002 // with this lookup and notification pretend that another intent 1003 // was received by calling onReceive() directly. 1004 if (mAccountLookupPending) { 1005 mAccountLookupPending = false; 1006 onReceive(context, intent); 1007 return; 1008 } 1009 } 1010 }, mMainHandler); 1011 } 1012 }; 1013 1014 /** 1015 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}. 1016 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 1017 * in or the main thread's Handler if handler is null. 1018 * @param listener the listener to add 1019 * @param handler the Handler whose thread will be used to invoke the listener. If null 1020 * the AccountManager context's main thread will be used. 1021 * @param updateImmediately if true then the listener will be invoked as a result of this 1022 * call. 1023 * @throws IllegalArgumentException if listener is null 1024 * @throws IllegalStateException if listener was already added 1025 */ 1026 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener, 1027 Handler handler, boolean updateImmediately) { 1028 if (listener == null) { 1029 throw new IllegalArgumentException("the listener is null"); 1030 } 1031 synchronized (mAccountsUpdatedListeners) { 1032 if (mAccountsUpdatedListeners.containsKey(listener)) { 1033 throw new IllegalStateException("this listener is already added"); 1034 } 1035 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1036 1037 mAccountsUpdatedListeners.put(listener, handler); 1038 1039 if (wasEmpty) { 1040 // Register a broadcast receiver to monitor account changes 1041 IntentFilter intentFilter = new IntentFilter(); 1042 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); 1043 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1044 } 1045 } 1046 1047 if (updateImmediately) { 1048 getAccounts(new Future1Callback<Account[]>() { 1049 public void run(Future1<Account[]> future) { 1050 try { 1051 listener.onAccountsUpdated(future.getResult()); 1052 } catch (OperationCanceledException e) { 1053 // ignore 1054 } 1055 } 1056 }, handler); 1057 } 1058 } 1059 1060 /** 1061 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with 1062 * {@link #addOnAccountsUpdatedListener}. 1063 * @param listener the listener to remove 1064 * @throws IllegalArgumentException if listener is null 1065 * @throws IllegalStateException if listener was not already added 1066 */ 1067 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) { 1068 if (listener == null) { 1069 throw new IllegalArgumentException("the listener is null"); 1070 } 1071 synchronized (mAccountsUpdatedListeners) { 1072 if (mAccountsUpdatedListeners.remove(listener) == null) { 1073 throw new IllegalStateException("this listener was not previously added"); 1074 } 1075 if (mAccountsUpdatedListeners.isEmpty()) { 1076 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1077 } 1078 } 1079 } 1080} 1081