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