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