AccountManager.java revision a698f4276968d078b1b9e2f3738c4f559a3307b2
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; 26 27import java.io.IOException; 28import java.util.concurrent.Callable; 29import java.util.concurrent.CancellationException; 30import java.util.concurrent.ExecutionException; 31import java.util.concurrent.FutureTask; 32import java.util.concurrent.TimeoutException; 33import java.util.concurrent.TimeUnit; 34 35/** 36 * A class that helps with interactions with the {@link IAccountManager} interface. It provides 37 * methods to allow for account, password, and authtoken management for all accounts on the 38 * device. Some of these calls are implemented with the help of the corresponding 39 * {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling: 40 * AccountManager accountManager = AccountManager.get(context); 41 * 42 * <p> 43 * TODO(fredq) this interface is still in flux 44 */ 45public class AccountManager { 46 private static final String TAG = "AccountManager"; 47 48 private final Context mContext; 49 private final IAccountManager mService; 50 51 public AccountManager(Context context, IAccountManager service) { 52 mContext = context; 53 mService = service; 54 } 55 56 public static AccountManager get(Context context) { 57 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 58 } 59 60 public String blockingGetPassword(Account account) { 61 ensureNotOnMainThread(); 62 try { 63 return mService.getPassword(account); 64 } catch (RemoteException e) { 65 // if this happens the entire runtime will restart 66 throw new RuntimeException(e); 67 } 68 } 69 70 public Future1<String> getPassword(final Future1Callback<String> callback, 71 final Account account, final Handler handler) { 72 return startAsFuture(callback, handler, new Callable<String>() { 73 public String call() throws Exception { 74 return blockingGetPassword(account); 75 } 76 }); 77 } 78 79 public String blockingGetUserData(Account account, String key) { 80 ensureNotOnMainThread(); 81 try { 82 return mService.getUserData(account, key); 83 } catch (RemoteException e) { 84 // if this happens the entire runtime will restart 85 throw new RuntimeException(e); 86 } 87 } 88 89 public Future1<String> getUserData(Future1Callback<String> callback, 90 final Account account, final String key, Handler handler) { 91 return startAsFuture(callback, handler, new Callable<String>() { 92 public String call() throws Exception { 93 return blockingGetUserData(account, key); 94 } 95 }); 96 } 97 98 public String[] blockingGetAuthenticatorTypes() { 99 ensureNotOnMainThread(); 100 try { 101 return mService.getAuthenticatorTypes(); 102 } catch (RemoteException e) { 103 // if this happens the entire runtime will restart 104 throw new RuntimeException(e); 105 } 106 } 107 108 public Future1<String[]> getAuthenticatorTypes(Future1Callback<String[]> callback, 109 Handler handler) { 110 return startAsFuture(callback, handler, new Callable<String[]>() { 111 public String[] call() throws Exception { 112 return blockingGetAuthenticatorTypes(); 113 } 114 }); 115 } 116 117 public Account[] blockingGetAccounts() { 118 ensureNotOnMainThread(); 119 try { 120 return mService.getAccounts(); 121 } catch (RemoteException e) { 122 // if this happens the entire runtime will restart 123 throw new RuntimeException(e); 124 } 125 } 126 127 public Account[] blockingGetAccountsByType(String accountType) { 128 ensureNotOnMainThread(); 129 try { 130 return mService.getAccountsByType(accountType); 131 } catch (RemoteException e) { 132 // if this happens the entire runtime will restart 133 throw new RuntimeException(e); 134 } 135 } 136 137 public Future1<Account[]> getAccounts(Future1Callback<Account[]> callback, Handler handler) { 138 return startAsFuture(callback, handler, new Callable<Account[]>() { 139 public Account[] call() throws Exception { 140 return blockingGetAccounts(); 141 } 142 }); 143 } 144 145 public Future1<Account[]> getAccountsByType(Future1Callback<Account[]> callback, 146 final String type, Handler handler) { 147 return startAsFuture(callback, handler, new Callable<Account[]>() { 148 public Account[] call() throws Exception { 149 return blockingGetAccountsByType(type); 150 } 151 }); 152 } 153 154 public boolean blockingAddAccountExplicitly(Account account, String password, Bundle extras) { 155 ensureNotOnMainThread(); 156 try { 157 return mService.addAccount(account, password, extras); 158 } catch (RemoteException e) { 159 // if this happens the entire runtime will restart 160 throw new RuntimeException(e); 161 } 162 } 163 164 public Future1<Boolean> addAccountExplicitly(final Future1Callback<Boolean> callback, 165 final Account account, final String password, final Bundle extras, 166 final Handler handler) { 167 return startAsFuture(callback, handler, new Callable<Boolean>() { 168 public Boolean call() throws Exception { 169 return blockingAddAccountExplicitly(account, password, extras); 170 } 171 }); 172 } 173 174 public void blockingRemoveAccount(Account account) { 175 ensureNotOnMainThread(); 176 try { 177 mService.removeAccount(account); 178 } catch (RemoteException e) { 179 // if this happens the entire runtime will restart 180 } 181 } 182 183 public Future1<Void> removeAccount(Future1Callback<Void> callback, final Account account, 184 final Handler handler) { 185 return startAsFuture(callback, handler, new Callable<Void>() { 186 public Void call() throws Exception { 187 blockingRemoveAccount(account); 188 return null; 189 } 190 }); 191 } 192 193 public void blockingInvalidateAuthToken(String accountType, String authToken) { 194 ensureNotOnMainThread(); 195 try { 196 mService.invalidateAuthToken(accountType, authToken); 197 } catch (RemoteException e) { 198 // if this happens the entire runtime will restart 199 } 200 } 201 202 public Future1<Void> invalidateAuthToken(Future1Callback<Void> callback, 203 final String accountType, final String authToken, final Handler handler) { 204 return startAsFuture(callback, handler, new Callable<Void>() { 205 public Void call() throws Exception { 206 blockingInvalidateAuthToken(accountType, authToken); 207 return null; 208 } 209 }); 210 } 211 212 public String blockingPeekAuthToken(Account account, String authTokenType) { 213 ensureNotOnMainThread(); 214 try { 215 return mService.peekAuthToken(account, authTokenType); 216 } catch (RemoteException e) { 217 // if this happens the entire runtime will restart 218 throw new RuntimeException(e); 219 } 220 } 221 222 public Future1<String> peekAuthToken(Future1Callback<String> callback, 223 final Account account, final String authTokenType, final Handler handler) { 224 return startAsFuture(callback, handler, new Callable<String>() { 225 public String call() throws Exception { 226 return blockingPeekAuthToken(account, authTokenType); 227 } 228 }); 229 } 230 231 public void blockingSetPassword(Account account, String password) { 232 ensureNotOnMainThread(); 233 try { 234 mService.setPassword(account, password); 235 } catch (RemoteException e) { 236 // if this happens the entire runtime will restart 237 } 238 } 239 240 public Future1<Void> setPassword(Future1Callback<Void> callback, 241 final Account account, final String password, final Handler handler) { 242 return startAsFuture(callback, handler, new Callable<Void>() { 243 public Void call() throws Exception { 244 blockingSetPassword(account, password); 245 return null; 246 } 247 }); 248 } 249 250 public void blockingClearPassword(Account account) { 251 ensureNotOnMainThread(); 252 try { 253 mService.clearPassword(account); 254 } catch (RemoteException e) { 255 // if this happens the entire runtime will restart 256 } 257 } 258 259 public Future1<Void> clearPassword(final Future1Callback<Void> callback, final Account account, 260 final Handler handler) { 261 return startAsFuture(callback, handler, new Callable<Void>() { 262 public Void call() throws Exception { 263 blockingClearPassword(account); 264 return null; 265 } 266 }); 267 } 268 269 public void blockingSetUserData(Account account, String key, String value) { 270 ensureNotOnMainThread(); 271 try { 272 mService.setUserData(account, key, value); 273 } catch (RemoteException e) { 274 // if this happens the entire runtime will restart 275 } 276 } 277 278 public Future1<Void> setUserData(Future1Callback<Void> callback, 279 final Account account, final String key, final String value, final Handler handler) { 280 return startAsFuture(callback, handler, new Callable<Void>() { 281 public Void call() throws Exception { 282 blockingSetUserData(account, key, value); 283 return null; 284 } 285 }); 286 } 287 288 public void blockingSetAuthToken(Account account, String authTokenType, String authToken) { 289 ensureNotOnMainThread(); 290 try { 291 mService.setAuthToken(account, authTokenType, authToken); 292 } catch (RemoteException e) { 293 // if this happens the entire runtime will restart 294 } 295 } 296 297 public Future1<Void> setAuthToken(Future1Callback<Void> callback, 298 final Account account, final String authTokenType, final String authToken, 299 final Handler handler) { 300 return startAsFuture(callback, handler, new Callable<Void>() { 301 public Void call() throws Exception { 302 blockingSetAuthToken(account, authTokenType, authToken); 303 return null; 304 } 305 }); 306 } 307 308 public String blockingGetAuthToken(Account account, String authTokenType, 309 boolean notifyAuthFailure) 310 throws OperationCanceledException, IOException, AuthenticatorException { 311 ensureNotOnMainThread(); 312 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 313 null /* handler */).getResult(); 314 return bundle.getString(Constants.AUTHTOKEN_KEY); 315 } 316 317 /** 318 * Request the auth token for this account/authTokenType. If this succeeds then the 319 * auth token will then be passed to the activity. If this results in an authentication 320 * failure then a login intent will be returned that can be invoked to prompt the user to 321 * update their credentials. This login activity will return the auth token to the calling 322 * activity. If activity is null then the login intent will not be invoked. 323 * 324 * @param account the account whose auth token should be retrieved 325 * @param authTokenType the auth token type that should be retrieved 326 * @param loginOptions 327 * @param activity the activity to launch the login intent, if necessary, and to which 328 */ 329 public Future2 getAuthToken( 330 final Account account, final String authTokenType, final Bundle loginOptions, 331 final Activity activity, Future2Callback callback, Handler handler) { 332 if (activity == null) throw new IllegalArgumentException("activity is null"); 333 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 334 return new AmsTask(activity, handler, callback) { 335 public void doWork() throws RemoteException { 336 mService.getAuthToken(mResponse, account, authTokenType, 337 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 338 loginOptions); 339 } 340 }; 341 } 342 343 public Future2 getAuthToken( 344 final Account account, final String authTokenType, final boolean notifyAuthFailure, 345 Future2Callback callback, Handler handler) { 346 if (account == null) throw new IllegalArgumentException("account is null"); 347 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 348 return new AmsTask(null, handler, callback) { 349 public void doWork() throws RemoteException { 350 mService.getAuthToken(mResponse, account, authTokenType, 351 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 352 } 353 }; 354 } 355 356 public Future2 addAccount(final String accountType, 357 final String authTokenType, final Bundle addAccountOptions, 358 final Activity activity, Future2Callback callback, Handler handler) { 359 return new AmsTask(activity, handler, callback) { 360 public void doWork() throws RemoteException { 361 mService.addAcount(mResponse, accountType, authTokenType, 362 activity != null, addAccountOptions); 363 } 364 }; 365 } 366 367 /** @deprecated use {@link #confirmCredentials} instead */ 368 public Future1<Boolean> confirmPassword(final Account account, final String password, 369 Future1Callback<Boolean> callback, Handler handler) { 370 return new AMSTaskBoolean(handler, callback) { 371 public void doWork() throws RemoteException { 372 mService.confirmPassword(response, account, password); 373 } 374 }; 375 } 376 377 public Future2 confirmCredentials(final Account account, final Activity activity, 378 final Future2Callback callback, 379 final Handler handler) { 380 return new AmsTask(activity, handler, callback) { 381 public void doWork() throws RemoteException { 382 mService.confirmCredentials(mResponse, account, activity != null); 383 } 384 }; 385 } 386 387 public Future2 updateCredentials(final Account account, final String authTokenType, 388 final Bundle loginOptions, final Activity activity, 389 final Future2Callback callback, 390 final Handler handler) { 391 return new AmsTask(activity, handler, callback) { 392 public void doWork() throws RemoteException { 393 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 394 loginOptions); 395 } 396 }; 397 } 398 399 public Future2 editProperties(final String accountType, final Activity activity, 400 final Future2Callback callback, 401 final Handler handler) { 402 return new AmsTask(activity, handler, callback) { 403 public void doWork() throws RemoteException { 404 mService.editProperties(mResponse, accountType, activity != null); 405 } 406 }; 407 } 408 409 private void ensureNotOnMainThread() { 410 final Looper looper = Looper.myLooper(); 411 if (looper != null && looper == mContext.getMainLooper()) { 412 // We really want to throw an exception here, but GTalkService exercises this 413 // path quite a bit and needs some serious rewrite in order to work properly. 414 //noinspection ThrowableInstanceNeverThrow 415// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 416// new Exception()); 417 // TODO(fredq) remove the log and throw this exception when the callers are fixed 418// throw new IllegalStateException( 419// "calling this from your main thread can lead to deadlock"); 420 } 421 } 422 423 private void postToHandler(Handler handler, final Future2Callback callback, 424 final Future2 future) { 425 if (handler == null) { 426 handler = new Handler(mContext.getMainLooper()); 427 } 428 final Handler innerHandler = handler; 429 innerHandler.post(new Runnable() { 430 public void run() { 431 callback.run(future); 432 } 433 }); 434 } 435 436 private <V> void postToHandler(Handler handler, final Future1Callback<V> callback, 437 final Future1<V> future) { 438 if (handler == null) { 439 handler = new Handler(mContext.getMainLooper()); 440 } 441 final Handler innerHandler = handler; 442 innerHandler.post(new Runnable() { 443 public void run() { 444 callback.run(future); 445 } 446 }); 447 } 448 449 private <V> Future1<V> startAsFuture(Future1Callback<V> callback, Handler handler, 450 Callable<V> callable) { 451 final FutureTaskWithCallback<V> task = 452 new FutureTaskWithCallback<V>(callback, callable, handler); 453 new Thread(task).start(); 454 return task; 455 } 456 457 private class FutureTaskWithCallback<V> extends FutureTask<V> implements Future1<V> { 458 final Future1Callback<V> mCallback; 459 final Handler mHandler; 460 461 public FutureTaskWithCallback(Future1Callback<V> callback, Callable<V> callable, 462 Handler handler) { 463 super(callable); 464 mCallback = callback; 465 mHandler = handler; 466 } 467 468 protected void done() { 469 if (mCallback != null) { 470 postToHandler(mHandler, mCallback, this); 471 } 472 } 473 474 public V internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException { 475 try { 476 if (timeout == null) { 477 return get(); 478 } else { 479 return get(timeout, unit); 480 } 481 } catch (InterruptedException e) { 482 // we will cancel the task below 483 } catch (CancellationException e) { 484 // we will cancel the task below 485 } catch (TimeoutException e) { 486 // we will cancel the task below 487 } catch (ExecutionException e) { 488 // this should never happen 489 throw new IllegalStateException(e.getCause()); 490 } finally { 491 cancel(true /* interruptIfRunning */); 492 } 493 throw new OperationCanceledException(); 494 } 495 496 public V getResult() throws OperationCanceledException { 497 return internalGetResult(null, null); 498 } 499 500 public V getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 501 return internalGetResult(null, null); 502 } 503 } 504 505 public abstract class AmsTask extends FutureTask<Bundle> implements Future2 { 506 final IAccountManagerResponse mResponse; 507 final Handler mHandler; 508 final Future2Callback mCallback; 509 final Activity mActivity; 510 public AmsTask(Activity activity, Handler handler, Future2Callback callback) { 511 super(new Callable<Bundle>() { 512 public Bundle call() throws Exception { 513 throw new IllegalStateException("this should never be called"); 514 } 515 }); 516 517 mHandler = handler; 518 mCallback = callback; 519 mActivity = activity; 520 mResponse = new Response(); 521 522 new Thread(new Runnable() { 523 public void run() { 524 try { 525 doWork(); 526 } catch (RemoteException e) { 527 // never happens 528 } 529 } 530 }).start(); 531 } 532 533 public abstract void doWork() throws RemoteException; 534 535 private Bundle internalGetResult(Long timeout, TimeUnit unit) 536 throws OperationCanceledException, IOException, AuthenticatorException { 537 try { 538 if (timeout == null) { 539 return get(); 540 } else { 541 return get(timeout, unit); 542 } 543 } catch (CancellationException e) { 544 throw new OperationCanceledException(); 545 } catch (TimeoutException e) { 546 // fall through and cancel 547 } catch (InterruptedException e) { 548 // fall through and cancel 549 } catch (ExecutionException e) { 550 final Throwable cause = e.getCause(); 551 if (cause instanceof IOException) { 552 throw (IOException) cause; 553 } else if (cause instanceof UnsupportedOperationException) { 554 throw new AuthenticatorException(cause); 555 } else if (cause instanceof AuthenticatorException) { 556 throw (AuthenticatorException) cause; 557 } else if (cause instanceof RuntimeException) { 558 throw (RuntimeException) cause; 559 } else if (cause instanceof Error) { 560 throw (Error) cause; 561 } else { 562 throw new IllegalStateException(cause); 563 } 564 } finally { 565 cancel(true /* interrupt if running */); 566 } 567 throw new OperationCanceledException(); 568 } 569 570 public Bundle getResult() 571 throws OperationCanceledException, IOException, AuthenticatorException { 572 return internalGetResult(null, null); 573 } 574 575 public Bundle getResult(long timeout, TimeUnit unit) 576 throws OperationCanceledException, IOException, AuthenticatorException { 577 return internalGetResult(timeout, unit); 578 } 579 580 protected void done() { 581 if (mCallback != null) { 582 postToHandler(mHandler, mCallback, this); 583 } 584 } 585 586 /** Handles the responses from the AccountManager */ 587 private class Response extends IAccountManagerResponse.Stub { 588 public void onResult(Bundle bundle) { 589 Intent intent = bundle.getParcelable("intent"); 590 if (intent != null && mActivity != null) { 591 // since the user provided an Activity we will silently start intents 592 // that we see 593 mActivity.startActivity(intent); 594 // leave the Future running to wait for the real response to this request 595 } else { 596 set(bundle); 597 } 598 } 599 600 public void onError(int code, String message) { 601 if (code == Constants.ERROR_CODE_CANCELED) { 602 // the authenticator indicated that this request was canceled, do so now 603 cancel(true /* mayInterruptIfRunning */); 604 return; 605 } 606 setException(convertErrorToException(code, message)); 607 } 608 } 609 610 } 611 612 public abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> { 613 final IAccountManagerResponse response; 614 final Handler mHandler; 615 final Future1Callback<Boolean> mCallback; 616 public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) { 617 super(new Callable<Boolean>() { 618 public Boolean call() throws Exception { 619 throw new IllegalStateException("this should never be called"); 620 } 621 }); 622 623 mHandler = handler; 624 mCallback = callback; 625 response = new Response(); 626 627 new Thread(new Runnable() { 628 public void run() { 629 try { 630 doWork(); 631 } catch (RemoteException e) { 632 // never happens 633 } 634 } 635 }).start(); 636 } 637 638 public abstract void doWork() throws RemoteException; 639 640 641 protected void done() { 642 if (mCallback != null) { 643 postToHandler(mHandler, mCallback, this); 644 } 645 } 646 647 private Boolean internalGetResult(Long timeout, TimeUnit unit) { 648 try { 649 if (timeout == null) { 650 return get(); 651 } else { 652 return get(timeout, unit); 653 } 654 } catch (InterruptedException e) { 655 // fall through and cancel 656 } catch (TimeoutException e) { 657 // fall through and cancel 658 } catch (CancellationException e) { 659 return false; 660 } catch (ExecutionException e) { 661 final Throwable cause = e.getCause(); 662 if (cause instanceof IOException) { 663 return false; 664 } else if (cause instanceof UnsupportedOperationException) { 665 return false; 666 } else if (cause instanceof AuthenticatorException) { 667 return false; 668 } else if (cause instanceof RuntimeException) { 669 throw (RuntimeException) cause; 670 } else if (cause instanceof Error) { 671 throw (Error) cause; 672 } else { 673 throw new IllegalStateException(cause); 674 } 675 } finally { 676 cancel(true /* interrupt if running */); 677 } 678 return false; 679 } 680 681 public Boolean getResult() throws OperationCanceledException { 682 return internalGetResult(null, null); 683 } 684 685 public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException { 686 return internalGetResult(timeout, unit); 687 } 688 689 private class Response extends IAccountManagerResponse.Stub { 690 public void onResult(Bundle bundle) { 691 try { 692 if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { 693 set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY)); 694 return; 695 } 696 } catch (ClassCastException e) { 697 // we will set the exception below 698 } 699 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); 700 } 701 702 public void onError(int code, String message) { 703 if (code == Constants.ERROR_CODE_CANCELED) { 704 cancel(true /* mayInterruptIfRunning */); 705 return; 706 } 707 setException(convertErrorToException(code, message)); 708 } 709 } 710 711 } 712 713 private Exception convertErrorToException(int code, String message) { 714 if (code == Constants.ERROR_CODE_NETWORK_ERROR) { 715 return new IOException(message); 716 } 717 718 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { 719 return new UnsupportedOperationException(); 720 } 721 722 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { 723 return new AuthenticatorException("invalid response"); 724 } 725 726 return new AuthenticatorException("unknown error code"); 727 } 728} 729