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