AccountManager.java revision 3326920329cecb57c7ff1fc5c6add5c98aab9ed9
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.os.Bundle; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.RemoteException; 26import android.os.Parcelable; 27 28import java.io.IOException; 29import java.util.concurrent.Callable; 30import java.util.concurrent.CancellationException; 31import java.util.concurrent.ExecutionException; 32import java.util.concurrent.FutureTask; 33import java.util.concurrent.TimeoutException; 34import java.util.concurrent.TimeUnit; 35 36/** 37 * A class that helps with interactions with the AccountManagerService. It provides 38 * methods to allow for account, password, and authtoken management for all accounts on the 39 * device. Some of these calls are implemented with the help of the corresponding 40 * {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling: 41 * AccountManager accountManager = AccountManager.get(context); 42 * 43 * <p> 44 * TODO(fredq) this interface is still in flux 45 */ 46public class AccountManager { 47 private static final String TAG = "AccountManager"; 48 49 private final Context mContext; 50 private final IAccountManager mService; 51 52 /** 53 * @hide 54 */ 55 public AccountManager(Context context, IAccountManager service) { 56 mContext = context; 57 mService = service; 58 } 59 60 public static AccountManager get(Context context) { 61 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 62 } 63 64 public String blockingGetPassword(Account account) { 65 ensureNotOnMainThread(); 66 try { 67 return mService.getPassword(account); 68 } catch (RemoteException e) { 69 // if this happens the entire runtime will restart 70 throw new RuntimeException(e); 71 } 72 } 73 74 public Future1<String> getPassword(final Future1Callback<String> callback, 75 final Account account, final Handler handler) { 76 return startAsFuture(callback, handler, new Callable<String>() { 77 public String call() throws Exception { 78 return blockingGetPassword(account); 79 } 80 }); 81 } 82 83 public String blockingGetUserData(Account account, String key) { 84 ensureNotOnMainThread(); 85 try { 86 return mService.getUserData(account, key); 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> getUserData(Future1Callback<String> callback, 94 final Account account, final String key, Handler handler) { 95 return startAsFuture(callback, handler, new Callable<String>() { 96 public String call() throws Exception { 97 return blockingGetUserData(account, key); 98 } 99 }); 100 } 101 102 public String[] blockingGetAuthenticatorTypes() { 103 ensureNotOnMainThread(); 104 try { 105 return mService.getAuthenticatorTypes(); 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[]> getAuthenticatorTypes(Future1Callback<String[]> callback, 113 Handler handler) { 114 return startAsFuture(callback, handler, new Callable<String[]>() { 115 public String[] call() throws Exception { 116 return blockingGetAuthenticatorTypes(); 117 } 118 }); 119 } 120 121 public Account[] blockingGetAccounts() { 122 ensureNotOnMainThread(); 123 try { 124 return mService.getAccounts(); 125 } catch (RemoteException e) { 126 // if this happens the entire runtime will restart 127 throw new RuntimeException(e); 128 } 129 } 130 131 public Account[] blockingGetAccountsByType(String accountType) { 132 ensureNotOnMainThread(); 133 try { 134 return mService.getAccountsByType(accountType); 135 } catch (RemoteException e) { 136 // if this happens the entire runtime will restart 137 throw new RuntimeException(e); 138 } 139 } 140 141 public Future1<Account[]> getAccounts(Future1Callback<Account[]> callback, Handler handler) { 142 return startAsFuture(callback, handler, new Callable<Account[]>() { 143 public Account[] call() throws Exception { 144 return blockingGetAccounts(); 145 } 146 }); 147 } 148 149 public Future1<Account[]> getAccountsByType(Future1Callback<Account[]> callback, 150 final String type, Handler handler) { 151 return startAsFuture(callback, handler, new Callable<Account[]>() { 152 public Account[] call() throws Exception { 153 return blockingGetAccountsByType(type); 154 } 155 }); 156 } 157 158 public boolean blockingAddAccountExplicitly(Account account, String password, Bundle extras) { 159 ensureNotOnMainThread(); 160 try { 161 return mService.addAccount(account, password, extras); 162 } catch (RemoteException e) { 163 // if this happens the entire runtime will restart 164 throw new RuntimeException(e); 165 } 166 } 167 168 public Future1<Boolean> addAccountExplicitly(final Future1Callback<Boolean> callback, 169 final Account account, final String password, final Bundle extras, 170 final Handler handler) { 171 return startAsFuture(callback, handler, new Callable<Boolean>() { 172 public Boolean call() throws Exception { 173 return blockingAddAccountExplicitly(account, password, extras); 174 } 175 }); 176 } 177 178 public void blockingRemoveAccount(Account account) { 179 ensureNotOnMainThread(); 180 try { 181 mService.removeAccount(account); 182 } catch (RemoteException e) { 183 // if this happens the entire runtime will restart 184 } 185 } 186 187 public Future1<Void> removeAccount(Future1Callback<Void> callback, final Account account, 188 final Handler handler) { 189 return startAsFuture(callback, handler, new Callable<Void>() { 190 public Void call() throws Exception { 191 blockingRemoveAccount(account); 192 return null; 193 } 194 }); 195 } 196 197 public void blockingInvalidateAuthToken(String accountType, String authToken) { 198 ensureNotOnMainThread(); 199 try { 200 mService.invalidateAuthToken(accountType, authToken); 201 } catch (RemoteException e) { 202 // if this happens the entire runtime will restart 203 } 204 } 205 206 public Future1<Void> invalidateAuthToken(Future1Callback<Void> callback, 207 final String accountType, final String authToken, final Handler handler) { 208 return startAsFuture(callback, handler, new Callable<Void>() { 209 public Void call() throws Exception { 210 blockingInvalidateAuthToken(accountType, authToken); 211 return null; 212 } 213 }); 214 } 215 216 public String blockingPeekAuthToken(Account account, String authTokenType) { 217 ensureNotOnMainThread(); 218 try { 219 return mService.peekAuthToken(account, authTokenType); 220 } catch (RemoteException e) { 221 // if this happens the entire runtime will restart 222 throw new RuntimeException(e); 223 } 224 } 225 226 public Future1<String> peekAuthToken(Future1Callback<String> callback, 227 final Account account, final String authTokenType, final Handler handler) { 228 return startAsFuture(callback, handler, new Callable<String>() { 229 public String call() throws Exception { 230 return blockingPeekAuthToken(account, authTokenType); 231 } 232 }); 233 } 234 235 public void blockingSetPassword(Account account, String password) { 236 ensureNotOnMainThread(); 237 try { 238 mService.setPassword(account, password); 239 } catch (RemoteException e) { 240 // if this happens the entire runtime will restart 241 } 242 } 243 244 public Future1<Void> setPassword(Future1Callback<Void> callback, 245 final Account account, final String password, final Handler handler) { 246 return startAsFuture(callback, handler, new Callable<Void>() { 247 public Void call() throws Exception { 248 blockingSetPassword(account, password); 249 return null; 250 } 251 }); 252 } 253 254 public void blockingClearPassword(Account account) { 255 ensureNotOnMainThread(); 256 try { 257 mService.clearPassword(account); 258 } catch (RemoteException e) { 259 // if this happens the entire runtime will restart 260 } 261 } 262 263 public Future1<Void> clearPassword(final Future1Callback<Void> callback, final Account account, 264 final Handler handler) { 265 return startAsFuture(callback, handler, new Callable<Void>() { 266 public Void call() throws Exception { 267 blockingClearPassword(account); 268 return null; 269 } 270 }); 271 } 272 273 public void blockingSetUserData(Account account, String key, String value) { 274 ensureNotOnMainThread(); 275 try { 276 mService.setUserData(account, key, value); 277 } catch (RemoteException e) { 278 // if this happens the entire runtime will restart 279 } 280 } 281 282 public Future1<Void> setUserData(Future1Callback<Void> callback, 283 final Account account, final String key, final String value, final Handler handler) { 284 return startAsFuture(callback, handler, new Callable<Void>() { 285 public Void call() throws Exception { 286 blockingSetUserData(account, key, value); 287 return null; 288 } 289 }); 290 } 291 292 public void blockingSetAuthToken(Account account, String authTokenType, String authToken) { 293 ensureNotOnMainThread(); 294 try { 295 mService.setAuthToken(account, authTokenType, authToken); 296 } catch (RemoteException e) { 297 // if this happens the entire runtime will restart 298 } 299 } 300 301 public Future1<Void> setAuthToken(Future1Callback<Void> callback, 302 final Account account, final String authTokenType, final String authToken, 303 final Handler handler) { 304 return startAsFuture(callback, handler, new Callable<Void>() { 305 public Void call() throws Exception { 306 blockingSetAuthToken(account, authTokenType, authToken); 307 return null; 308 } 309 }); 310 } 311 312 public String blockingGetAuthToken(Account account, String authTokenType, 313 boolean notifyAuthFailure) 314 throws OperationCanceledException, IOException, AuthenticatorException { 315 ensureNotOnMainThread(); 316 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 317 null /* handler */).getResult(); 318 return bundle.getString(Constants.AUTHTOKEN_KEY); 319 } 320 321 /** 322 * Request the auth token for this account/authTokenType. If this succeeds then the 323 * auth token will then be passed to the activity. If this results in an authentication 324 * failure then a login intent will be returned that can be invoked to prompt the user to 325 * update their credentials. This login activity will return the auth token to the calling 326 * activity. If activity is null then the login intent will not be invoked. 327 * 328 * @param account the account whose auth token should be retrieved 329 * @param authTokenType the auth token type that should be retrieved 330 * @param loginOptions 331 * @param activity the activity to launch the login intent, if necessary, and to which 332 */ 333 public Future2 getAuthToken( 334 final Account account, final String authTokenType, final Bundle loginOptions, 335 final Activity activity, Future2Callback callback, Handler handler) { 336 if (activity == null) throw new IllegalArgumentException("activity is null"); 337 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 338 return new AmsTask(activity, handler, callback) { 339 public void doWork() throws RemoteException { 340 mService.getAuthToken(mResponse, account, authTokenType, 341 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 342 loginOptions); 343 } 344 }.start(); 345 } 346 347 public Future2 getAuthToken( 348 final Account account, final String authTokenType, final boolean notifyAuthFailure, 349 Future2Callback callback, Handler handler) { 350 if (account == null) throw new IllegalArgumentException("account is null"); 351 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 352 return new AmsTask(null, handler, callback) { 353 public void doWork() throws RemoteException { 354 mService.getAuthToken(mResponse, account, authTokenType, 355 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 356 } 357 }.start(); 358 } 359 360 public Future2 addAccount(final String accountType, 361 final String authTokenType, final String[] requiredFeatures, 362 final Bundle addAccountOptions, 363 final Activity activity, Future2Callback callback, Handler handler) { 364 return new AmsTask(activity, handler, callback) { 365 public void doWork() throws RemoteException { 366 mService.addAcount(mResponse, accountType, authTokenType, 367 requiredFeatures, activity != null, addAccountOptions); 368 } 369 }.start(); 370 } 371 372 /** @deprecated use {@link #confirmCredentials} instead */ 373 public Future1<Boolean> confirmPassword(final Account account, final String password, 374 Future1Callback<Boolean> callback, Handler handler) { 375 return new AMSTaskBoolean(handler, callback) { 376 public void doWork() throws RemoteException { 377 mService.confirmPassword(response, account, password); 378 } 379 }; 380 } 381 382 public Account[] blockingGetAccountsWithTypeAndFeatures(String type, String[] features) 383 throws AuthenticatorException, IOException, OperationCanceledException { 384 Future2 future = getAccountsWithTypeAndFeatures(type, features, 385 null /* callback */, null /* handler */); 386 Bundle result = future.getResult(); 387 Parcelable[] accountsTemp = result.getParcelableArray(Constants.ACCOUNTS_KEY); 388 if (accountsTemp == null) { 389 throw new AuthenticatorException("accounts should not be null"); 390 } 391 Account[] accounts = new Account[accountsTemp.length]; 392 for (int i = 0; i < accountsTemp.length; i++) { 393 accounts[i] = (Account) accountsTemp[i]; 394 } 395 return accounts; 396 } 397 398 public Future2 getAccountsWithTypeAndFeatures( 399 final String type, final String[] features, 400 Future2Callback callback, Handler handler) { 401 if (type == null) throw new IllegalArgumentException("type is null"); 402 return new AmsTask(null /* activity */, handler, callback) { 403 public void doWork() throws RemoteException { 404 mService.getAccountsByTypeAndFeatures(mResponse, type, features); 405 } 406 }.start(); 407 } 408 409 public Future2 confirmCredentials(final Account account, final Activity activity, 410 final Future2Callback callback, 411 final Handler handler) { 412 return new AmsTask(activity, handler, callback) { 413 public void doWork() throws RemoteException { 414 mService.confirmCredentials(mResponse, account, activity != null); 415 } 416 }.start(); 417 } 418 419 public Future2 updateCredentials(final Account account, final String authTokenType, 420 final Bundle loginOptions, final Activity activity, 421 final Future2Callback callback, 422 final Handler handler) { 423 return new AmsTask(activity, handler, callback) { 424 public void doWork() throws RemoteException { 425 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 426 loginOptions); 427 } 428 }.start(); 429 } 430 431 public Future2 editProperties(final String accountType, final Activity activity, 432 final Future2Callback callback, 433 final Handler handler) { 434 return new AmsTask(activity, handler, callback) { 435 public void doWork() throws RemoteException { 436 mService.editProperties(mResponse, accountType, activity != null); 437 } 438 }.start(); 439 } 440 441 private void ensureNotOnMainThread() { 442 final Looper looper = Looper.myLooper(); 443 if (looper != null && looper == mContext.getMainLooper()) { 444 // We really want to throw an exception here, but GTalkService exercises this 445 // path quite a bit and needs some serious rewrite in order to work properly. 446 //noinspection ThrowableInstanceNeverThrow 447// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 448// new Exception()); 449 // TODO(fredq) remove the log and throw this exception when the callers are fixed 450// throw new IllegalStateException( 451// "calling this from your main thread can lead to deadlock"); 452 } 453 } 454 455 private void postToHandler(Handler handler, final Future2Callback callback, 456 final Future2 future) { 457 if (handler == null) { 458 handler = new Handler(mContext.getMainLooper()); 459 } 460 final Handler innerHandler = handler; 461 innerHandler.post(new Runnable() { 462 public void run() { 463 callback.run(future); 464 } 465 }); 466 } 467 468 private <V> void postToHandler(Handler handler, final Future1Callback<V> callback, 469 final Future1<V> future) { 470 if (handler == null) { 471 handler = new Handler(mContext.getMainLooper()); 472 } 473 final Handler innerHandler = handler; 474 innerHandler.post(new Runnable() { 475 public void run() { 476 callback.run(future); 477 } 478 }); 479 } 480 481 private <V> Future1<V> startAsFuture(Future1Callback<V> callback, Handler handler, 482 Callable<V> callable) { 483 final FutureTaskWithCallback<V> task = 484 new FutureTaskWithCallback<V>(callback, callable, handler); 485 new Thread(task).start(); 486 return task; 487 } 488 489 private class FutureTaskWithCallback<V> extends FutureTask<V> implements Future1<V> { 490 final Future1Callback<V> mCallback; 491 final Handler mHandler; 492 493 public FutureTaskWithCallback(Future1Callback<V> callback, Callable<V> callable, 494 Handler handler) { 495 super(callable); 496 mCallback = callback; 497 mHandler = handler; 498 } 499 500 protected void done() { 501 if (mCallback != null) { 502 postToHandler(mHandler, mCallback, this); 503 } 504 } 505 506 public V internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException { 507 try { 508 if (timeout == null) { 509 return get(); 510 } else { 511 return get(timeout, unit); 512 } 513 } catch (InterruptedException e) { 514 // we will cancel the task below 515 } catch (CancellationException e) { 516 // we will cancel the task below 517 } catch (TimeoutException e) { 518 // we will cancel the task below 519 } catch (ExecutionException e) { 520 // this should never happen 521 throw new IllegalStateException(e.getCause()); 522 } finally { 523 cancel(true /* interruptIfRunning */); 524 } 525 throw new OperationCanceledException(); 526 } 527 528 public V getResult() throws OperationCanceledException { 529 return internalGetResult(null, null); 530 } 531 532 public V getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 533 return internalGetResult(null, null); 534 } 535 } 536 537 private abstract class AmsTask extends FutureTask<Bundle> implements Future2 { 538 final IAccountManagerResponse mResponse; 539 final Handler mHandler; 540 final Future2Callback mCallback; 541 final Activity mActivity; 542 final Thread mThread; 543 public AmsTask(Activity activity, Handler handler, Future2Callback callback) { 544 super(new Callable<Bundle>() { 545 public Bundle call() throws Exception { 546 throw new IllegalStateException("this should never be called"); 547 } 548 }); 549 550 mHandler = handler; 551 mCallback = callback; 552 mActivity = activity; 553 mResponse = new Response(); 554 mThread = new Thread(new Runnable() { 555 public void run() { 556 try { 557 doWork(); 558 } catch (RemoteException e) { 559 // never happens 560 } 561 } 562 }, "AmsTask"); 563 } 564 565 public final Future2 start() { 566 mThread.start(); 567 return this; 568 } 569 570 public abstract void doWork() throws RemoteException; 571 572 private Bundle internalGetResult(Long timeout, TimeUnit unit) 573 throws OperationCanceledException, IOException, AuthenticatorException { 574 try { 575 if (timeout == null) { 576 return get(); 577 } else { 578 return get(timeout, unit); 579 } 580 } catch (CancellationException e) { 581 throw new OperationCanceledException(); 582 } catch (TimeoutException e) { 583 // fall through and cancel 584 } catch (InterruptedException e) { 585 // fall through and cancel 586 } catch (ExecutionException e) { 587 final Throwable cause = e.getCause(); 588 if (cause instanceof IOException) { 589 throw (IOException) cause; 590 } else if (cause instanceof UnsupportedOperationException) { 591 throw new AuthenticatorException(cause); 592 } else if (cause instanceof AuthenticatorException) { 593 throw (AuthenticatorException) cause; 594 } else if (cause instanceof RuntimeException) { 595 throw (RuntimeException) cause; 596 } else if (cause instanceof Error) { 597 throw (Error) cause; 598 } else { 599 throw new IllegalStateException(cause); 600 } 601 } finally { 602 cancel(true /* interrupt if running */); 603 } 604 throw new OperationCanceledException(); 605 } 606 607 public Bundle getResult() 608 throws OperationCanceledException, IOException, AuthenticatorException { 609 return internalGetResult(null, null); 610 } 611 612 public Bundle getResult(long timeout, TimeUnit unit) 613 throws OperationCanceledException, IOException, AuthenticatorException { 614 return internalGetResult(timeout, unit); 615 } 616 617 protected void done() { 618 if (mCallback != null) { 619 postToHandler(mHandler, mCallback, this); 620 } 621 } 622 623 /** Handles the responses from the AccountManager */ 624 private class Response extends IAccountManagerResponse.Stub { 625 public void onResult(Bundle bundle) { 626 Intent intent = bundle.getParcelable("intent"); 627 if (intent != null && mActivity != null) { 628 // since the user provided an Activity we will silently start intents 629 // that we see 630 mActivity.startActivity(intent); 631 // leave the Future running to wait for the real response to this request 632 } else { 633 set(bundle); 634 } 635 } 636 637 public void onError(int code, String message) { 638 if (code == Constants.ERROR_CODE_CANCELED) { 639 // the authenticator indicated that this request was canceled, do so now 640 cancel(true /* mayInterruptIfRunning */); 641 return; 642 } 643 setException(convertErrorToException(code, message)); 644 } 645 } 646 647 } 648 649 private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> { 650 final IAccountManagerResponse response; 651 final Handler mHandler; 652 final Future1Callback<Boolean> mCallback; 653 public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) { 654 super(new Callable<Boolean>() { 655 public Boolean call() throws Exception { 656 throw new IllegalStateException("this should never be called"); 657 } 658 }); 659 660 mHandler = handler; 661 mCallback = callback; 662 response = new Response(); 663 664 new Thread(new Runnable() { 665 public void run() { 666 try { 667 doWork(); 668 } catch (RemoteException e) { 669 // never happens 670 } 671 } 672 }).start(); 673 } 674 675 public abstract void doWork() throws RemoteException; 676 677 678 protected void done() { 679 if (mCallback != null) { 680 postToHandler(mHandler, mCallback, this); 681 } 682 } 683 684 private Boolean internalGetResult(Long timeout, TimeUnit unit) { 685 try { 686 if (timeout == null) { 687 return get(); 688 } else { 689 return get(timeout, unit); 690 } 691 } catch (InterruptedException e) { 692 // fall through and cancel 693 } catch (TimeoutException e) { 694 // fall through and cancel 695 } catch (CancellationException e) { 696 return false; 697 } catch (ExecutionException e) { 698 final Throwable cause = e.getCause(); 699 if (cause instanceof IOException) { 700 return false; 701 } else if (cause instanceof UnsupportedOperationException) { 702 return false; 703 } else if (cause instanceof AuthenticatorException) { 704 return false; 705 } else if (cause instanceof RuntimeException) { 706 throw (RuntimeException) cause; 707 } else if (cause instanceof Error) { 708 throw (Error) cause; 709 } else { 710 throw new IllegalStateException(cause); 711 } 712 } finally { 713 cancel(true /* interrupt if running */); 714 } 715 return false; 716 } 717 718 public Boolean getResult() throws OperationCanceledException { 719 return internalGetResult(null, null); 720 } 721 722 public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 723 return internalGetResult(timeout, unit); 724 } 725 726 private class Response extends IAccountManagerResponse.Stub { 727 public void onResult(Bundle bundle) { 728 try { 729 if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 730 set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY)); 731 return; 732 } 733 } catch (ClassCastException e) { 734 // we will set the exception below 735 } 736 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 737 } 738 739 public void onError(int code, String message) { 740 if (code == Constants.ERROR_CODE_CANCELED) { 741 cancel(true /* mayInterruptIfRunning */); 742 return; 743 } 744 setException(convertErrorToException(code, message)); 745 } 746 } 747 748 } 749 750 private Exception convertErrorToException(int code, String message) { 751 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 752 return new IOException(message); 753 } 754 755 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 756 return new UnsupportedOperationException(message); 757 } 758 759 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 760 return new AuthenticatorException(message); 761 } 762 763 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) { 764 return new IllegalArgumentException(message); 765 } 766 767 return new AuthenticatorException(message); 768 } 769 770 private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback { 771 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 772 final String[] features, Activity activityForPrompting, 773 final Bundle addAccountOptions, final Bundle loginOptions, 774 Future2Callback callback, Handler handler) { 775 super(activityForPrompting, handler, callback); 776 if (accountType == null) throw new IllegalArgumentException("account type is null"); 777 mAccountType = accountType; 778 mAuthTokenType = authTokenType; 779 mFeatures = features; 780 mAddAccountOptions = addAccountOptions; 781 mLoginOptions = loginOptions; 782 mMyCallback = this; 783 } 784 volatile Future2 mFuture = null; 785 final String mAccountType; 786 final String mAuthTokenType; 787 final String[] mFeatures; 788 final Bundle mAddAccountOptions; 789 final Bundle mLoginOptions; 790 final Future2Callback mMyCallback; 791 792 public void doWork() throws RemoteException { 793 getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() { 794 public void run(Future2 future) { 795 Bundle getAccountsResult; 796 try { 797 getAccountsResult = future.getResult(); 798 } catch (OperationCanceledException e) { 799 setException(e); 800 return; 801 } catch (IOException e) { 802 setException(e); 803 return; 804 } catch (AuthenticatorException e) { 805 setException(e); 806 return; 807 } 808 809 Parcelable[] accounts = 810 getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY); 811 if (accounts.length == 0) { 812 if (mActivity != null) { 813 // no accounts, add one now. pretend that the user directly 814 // made this request 815 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 816 mAddAccountOptions, mActivity, mMyCallback, mHandler); 817 } else { 818 // send result since we can't prompt to add an account 819 Bundle result = new Bundle(); 820 result.putString(Constants.ACCOUNT_NAME_KEY, null); 821 result.putString(Constants.ACCOUNT_TYPE_KEY, null); 822 result.putString(Constants.AUTHTOKEN_KEY, null); 823 try { 824 mResponse.onResult(result); 825 } catch (RemoteException e) { 826 // this will never happen 827 } 828 // we are done 829 } 830 } else if (accounts.length == 1) { 831 // have a single account, return an authtoken for it 832 if (mActivity == null) { 833 mFuture = getAuthToken((Account) accounts[0], mAuthTokenType, 834 false /* notifyAuthFailure */, mMyCallback, mHandler); 835 } else { 836 mFuture = getAuthToken((Account) accounts[0], 837 mAuthTokenType, mLoginOptions, 838 mActivity, mMyCallback, mHandler); 839 } 840 } else { 841 if (mActivity != null) { 842 IAccountManagerResponse chooseResponse = 843 new IAccountManagerResponse.Stub() { 844 public void onResult(Bundle value) throws RemoteException { 845 Account account = new Account( 846 value.getString(Constants.ACCOUNT_NAME_KEY), 847 value.getString(Constants.ACCOUNT_TYPE_KEY)); 848 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 849 mActivity, mMyCallback, mHandler); 850 } 851 852 public void onError(int errorCode, String errorMessage) 853 throws RemoteException { 854 mResponse.onError(errorCode, errorMessage); 855 } 856 }; 857 // have many accounts, launch the chooser 858 Intent intent = new Intent(); 859 intent.setClassName("android", 860 "android.accounts.ChooseAccountActivity"); 861 intent.putExtra(Constants.ACCOUNTS_KEY, accounts); 862 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY, 863 new AccountManagerResponse(chooseResponse)); 864 mActivity.startActivity(intent); 865 // the result will arrive via the IAccountManagerResponse 866 } else { 867 // send result since we can't prompt to select an account 868 Bundle result = new Bundle(); 869 result.putString(Constants.ACCOUNTS_KEY, null); 870 try { 871 mResponse.onResult(result); 872 } catch (RemoteException e) { 873 // this will never happen 874 } 875 // we are done 876 } 877 } 878 }}, mHandler); 879 } 880 881 882 883 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying 884 // future that we create. We need to do things like have cancel cancel the mFuture, if set 885 // or to cause this to be canceled if mFuture isn't set. 886 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2. 887 888 public void run(Future2 future) { 889 try { 890 set(future.get()); 891 } catch (InterruptedException e) { 892 cancel(true); 893 } catch (CancellationException e) { 894 cancel(true); 895 } catch (ExecutionException e) { 896 setException(e.getCause()); 897 } 898 } 899 } 900 901 public void getAuthTokenByFeatures( 902 final String accountType, final String authTokenType, final String[] features, 903 final Activity activityForPrompting, final Bundle addAccountOptions, 904 final Bundle loginOptions, 905 final Future2Callback callback, final Handler handler) { 906 if (accountType == null) throw new IllegalArgumentException("account type is null"); 907 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 908 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 909 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start(); 910 } 911} 912