AccountManager.java revision 8570f7440780db5c9b410e033e843b0e80e2fd27
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accounts;
18
19import android.app.Activity;
20import android.content.Intent;
21import android.content.Context;
22import android.content.IntentFilter;
23import android.content.BroadcastReceiver;
24import android.database.SQLException;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.RemoteException;
29import android.os.Parcelable;
30import android.os.Build;
31import android.util.Log;
32
33import java.io.IOException;
34import java.util.concurrent.Callable;
35import java.util.concurrent.CancellationException;
36import java.util.concurrent.ExecutionException;
37import java.util.concurrent.FutureTask;
38import java.util.concurrent.TimeoutException;
39import java.util.concurrent.TimeUnit;
40import java.util.HashMap;
41import java.util.Map;
42
43import com.google.android.collect.Maps;
44
45/**
46 * A class that helps with interactions with the AccountManager Service. It provides
47 * methods to allow for account, password, and authtoken management for all accounts on the
48 * device. One accesses the {@link AccountManager} by calling:
49 * <pre>
50 *    AccountManager accountManager = AccountManager.get(context);
51 * </pre>
52 *
53 * <p>
54 * The AccountManager Service provides storage for the accounts known to the system,
55 * provides methods to manage them, and allows the registration of authenticators to
56 * which operations such as addAccount and getAuthToken are delegated.
57 * <p>
58 * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters.
59 * These calls return immediately but run asynchronously. If a callback is provided then
60 * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully
61 * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the
62 * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which
63 * either returns the result or throws an exception as appropriate.
64 * <p>
65 * The asynchronous request can be made blocking by not providing a callback and instead
66 * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will
67 * cause the running thread to block until the result is returned. Keep in mind that one
68 * should not block the main thread in this way. Instead one should either use a callback,
69 * thus making the call asynchronous, or make the blocking call on a separate thread.
70 * getResult() will throw an {@link IllegalStateException} if you call it from the main thread
71 * before the request has completed, i.e. before the callback has been invoked.
72 * <p>
73 * If one wants to ensure that the callback is invoked from a specific handler then they should
74 * pass the handler to the request. This makes it easier to ensure thread-safety by running
75 * all of one's logic from a single handler.
76 */
77public class AccountManager {
78    private static final String TAG = "AccountManager";
79
80    public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
81    public static final int ERROR_CODE_NETWORK_ERROR = 3;
82    public static final int ERROR_CODE_CANCELED = 4;
83    public static final int ERROR_CODE_INVALID_RESPONSE = 5;
84    public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
85    public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
86    public static final int ERROR_CODE_BAD_REQUEST = 8;
87
88    public static final String KEY_ACCOUNTS = "accounts";
89    public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
90    public static final String KEY_USERDATA = "userdata";
91    public static final String KEY_AUTHTOKEN = "authtoken";
92    public static final String KEY_PASSWORD = "password";
93    public static final String KEY_ACCOUNT_NAME = "authAccount";
94    public static final String KEY_ACCOUNT_TYPE = "accountType";
95    public static final String KEY_ERROR_CODE = "errorCode";
96    public static final String KEY_ERROR_MESSAGE = "errorMessage";
97    public static final String KEY_INTENT = "intent";
98    public static final String KEY_BOOLEAN_RESULT = "booleanResult";
99    public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
100    public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
101    public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
102    public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
103    public static final String ACTION_AUTHENTICATOR_INTENT =
104            "android.accounts.AccountAuthenticator";
105    public static final String AUTHENTICATOR_META_DATA_NAME =
106                    "android.accounts.AccountAuthenticator";
107    public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
108
109    private final Context mContext;
110    private final IAccountManager mService;
111    private final Handler mMainHandler;
112    /**
113     * Action sent as a broadcast Intent by the AccountsService
114     * when accounts are added to and/or removed from the device's
115     * database.
116     */
117    public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
118        "android.accounts.LOGIN_ACCOUNTS_CHANGED";
119
120    /**
121     * @hide
122     */
123    public AccountManager(Context context, IAccountManager service) {
124        mContext = context;
125        mService = service;
126        mMainHandler = new Handler(mContext.getMainLooper());
127    }
128
129    /**
130     * @hide used for testing only
131     */
132    public AccountManager(Context context, IAccountManager service, Handler handler) {
133        mContext = context;
134        mService = service;
135        mMainHandler = handler;
136    }
137
138    /**
139     * Retrieve an AccountManager instance that is associated with the context that is passed in.
140     * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally,
141     * so the caller must take care to use a {@link Context} whose lifetime is associated with
142     * the listener registration.
143     * @param context The {@link Context} to use when necessary
144     * @return an {@link AccountManager} instance that is associated with context
145     */
146    public static AccountManager get(Context context) {
147        return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
148    }
149
150    /**
151     * Get the password that is associated with the account. Returns null if the account does
152     * not exist.
153     * <p>
154     * It is safe to call this method from the main thread.
155     * <p>
156     * Requires that the caller has permission
157     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
158     * with the same UID as the Authenticator for the account.
159     */
160    public String getPassword(final Account account) {
161        try {
162            return mService.getPassword(account);
163        } catch (RemoteException e) {
164            // will never happen
165            throw new RuntimeException(e);
166        }
167    }
168
169    /**
170     * Get the user data named by "key" that is associated with the account.
171     * Returns null if the account does not exist or if it does not have a value for key.
172     * <p>
173     * It is safe to call this method from the main thread.
174     * <p>
175     * Requires that the caller has permission
176     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
177     * with the same UID as the Authenticator for the account.
178     */
179    public String getUserData(final Account account, final String key) {
180        try {
181            return mService.getUserData(account, key);
182        } catch (RemoteException e) {
183            // will never happen
184            throw new RuntimeException(e);
185        }
186    }
187
188    /**
189     * Query the AccountManager Service for an array that contains a
190     * {@link AuthenticatorDescription} for each registered authenticator.
191     * @return an array that contains all the authenticators known to the AccountManager service.
192     * This array will be empty if there are no authenticators and will never return null.
193     * <p>
194     * It is safe to call this method from the main thread.
195     * <p>
196     * No permission is required to make this call.
197     */
198    public AuthenticatorDescription[] getAuthenticatorTypes() {
199        try {
200            return mService.getAuthenticatorTypes();
201        } catch (RemoteException e) {
202            // will never happen
203            throw new RuntimeException(e);
204        }
205    }
206
207    /**
208     * Query the AccountManager Service for all accounts.
209     * @return an array that contains all the accounts known to the AccountManager service.
210     * This array will be empty if there are no accounts and will never return null.
211     * <p>
212     * It is safe to call this method from the main thread.
213     * <p>
214     * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
215     */
216    public Account[] getAccounts() {
217        try {
218            return mService.getAccounts(null);
219        } catch (RemoteException e) {
220            // won't ever happen
221            throw new RuntimeException(e);
222        }
223    }
224
225    /**
226     * Query the AccountManager for the set of accounts that have a given type. If null
227     * is passed as the type than all accounts are returned.
228     * @param type the account type by which to filter, or null to get all accounts
229     * @return an array that contains the accounts that match the specified type. This array
230     * will be empty if no accounts match. It will never return null.
231     * <p>
232     * It is safe to call this method from the main thread.
233     * <p>
234     * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
235     */
236    public Account[] getAccountsByType(String type) {
237        try {
238            return mService.getAccounts(type);
239        } catch (RemoteException e) {
240            // won't ever happen
241            throw new RuntimeException(e);
242        }
243    }
244
245    /**
246     * Tests that the given account has the specified features. If this account does not exist
247     * then this call returns false.
248     * <p>
249     * This call returns immediately but runs asynchronously and the result is accessed via the
250     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
251     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
252     * method asynchronously then they will generally pass in a callback object that will get
253     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
254     * they will generally pass null for the callback and instead call
255     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
256     * which will then block until the request completes.
257     * <p>
258     * Do not block the main thread waiting this method's result.
259     * <p>
260     * Not allowed from main thread (but allowed from other threads):
261     * <pre>
262     * Boolean result = hasFeatures(account, features, callback, handler).getResult();
263     * </pre>
264     * Allowed from main thread:
265     * <pre>
266     * hasFeatures(account, features, new AccountManagerCallback<Boolean>() {
267     *    public void run(AccountManagerFuture<Boolean> future) {
268     *        Boolean result = future.getResult();
269     *        // use result
270     *    }
271     * }, handler);
272     * </pre>
273     * <p>
274     * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
275     *
276     * @param account The {@link Account} to test
277     * @param features the features for which to test
278     * @param callback A callback to invoke when the request completes. If null then
279     * no callback is invoked.
280     * @param handler The {@link Handler} to use to invoke the callback. If null then the
281     * main thread's {@link Handler} is used.
282     * @return an {@link AccountManagerFuture} that represents the future result of the call.
283     * The future result is a {@link Boolean} that is true if the account exists and has the
284     * specified features.
285     */
286    public AccountManagerFuture<Boolean> hasFeatures(final Account account,
287            final String[] features,
288            AccountManagerCallback<Boolean> callback, Handler handler) {
289        return new Future2Task<Boolean>(handler, callback) {
290            public void doWork() throws RemoteException {
291                mService.hasFeatures(mResponse, account, features);
292            }
293            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
294                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
295                    throw new AuthenticatorException("no result in response");
296                }
297                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
298            }
299        }.start();
300    }
301
302    /**
303     * Add an account to the AccountManager's set of known accounts.
304     * <p>
305     * It is safe to call this method from the main thread.
306     * <p>
307     * Requires that the caller has permission
308     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
309     * with the same UID as the Authenticator for the account.
310     * @param account The account to add
311     * @param password The password to associate with the account. May be null.
312     * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null.
313     * @return true if the account was sucessfully added, false otherwise, for example,
314     * if the account already exists or if the account is null
315     */
316    public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
317        try {
318            return mService.addAccount(account, password, userdata);
319        } catch (RemoteException e) {
320            // won't ever happen
321            throw new RuntimeException(e);
322        }
323    }
324
325    /**
326     * Removes the given account. If this account does not exist then this call has no effect.
327     * <p>
328     * This call returns immediately but runs asynchronously and the result is accessed via the
329     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
330     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
331     * method asynchronously then they will generally pass in a callback object that will get
332     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
333     * they will generally pass null for the callback and instead call
334     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
335     * which will then block until the request completes.
336     * <p>
337     * Do not block the main thread waiting this method's result.
338     * <p>
339     * Not allowed from main thread (but allowed from other threads):
340     * <pre>
341     * Boolean result = removeAccount(account, callback, handler).getResult();
342     * </pre>
343     * Allowed from main thread:
344     * <pre>
345     * removeAccount(account, new AccountManagerCallback<Boolean>() {
346     *    public void run(AccountManagerFuture<Boolean> future) {
347     *        Boolean result = future.getResult();
348     *        // use result
349     *    }
350     * }, handler);
351     * </pre>
352     * <p>
353     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
354     *
355     * @param account The {@link Account} to remove
356     * @param callback A callback to invoke when the request completes. If null then
357     * no callback is invoked.
358     * @param handler The {@link Handler} to use to invoke the callback. If null then the
359     * main thread's {@link Handler} is used.
360     * @return an {@link AccountManagerFuture} that represents the future result of the call.
361     * The future result is a {@link Boolean} that is true if the account is successfully removed
362     * or false if the authenticator refuses to remove the account.
363     */
364    public AccountManagerFuture<Boolean> removeAccount(final Account account,
365            AccountManagerCallback<Boolean> callback, Handler handler) {
366        return new Future2Task<Boolean>(handler, callback) {
367            public void doWork() throws RemoteException {
368                mService.removeAccount(mResponse, account);
369            }
370            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
371                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
372                    throw new AuthenticatorException("no result in response");
373                }
374                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
375            }
376        }.start();
377    }
378
379    /**
380     * Removes the given authtoken. If this authtoken does not exist for the given account type
381     * then this call has no effect.
382     * <p>
383     * It is safe to call this method from the main thread.
384     * <p>
385     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
386     * @param accountType the account type of the authtoken to invalidate
387     * @param authToken the authtoken to invalidate
388     */
389    public void invalidateAuthToken(final String accountType, final String authToken) {
390        try {
391            mService.invalidateAuthToken(accountType, authToken);
392        } catch (RemoteException e) {
393            // won't ever happen
394            throw new RuntimeException(e);
395        }
396    }
397
398    /**
399     * Gets the authtoken named by "authTokenType" for the specified account if it is cached
400     * by the AccountManager. If no authtoken is cached then null is returned rather than
401     * asking the authenticaticor to generate one. If the account or the
402     * authtoken do not exist then null is returned.
403     * <p>
404     * It is safe to call this method from the main thread.
405     * <p>
406     * Requires that the caller has permission
407     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
408     * with the same UID as the Authenticator for the account.
409     * @param account the account whose authtoken is to be retrieved, must not be null
410     * @param authTokenType the type of authtoken to retrieve
411     * @return an authtoken for the given account and authTokenType, if one is cached by the
412     * AccountManager, null otherwise.
413     */
414    public String peekAuthToken(final Account account, final String authTokenType) {
415        if (account == null) {
416            Log.e(TAG, "peekAuthToken: the account must not be null");
417            return null;
418        }
419        if (authTokenType == null) {
420            return null;
421        }
422        try {
423            return mService.peekAuthToken(account, authTokenType);
424        } catch (RemoteException e) {
425            // won't ever happen
426            throw new RuntimeException(e);
427        }
428    }
429
430    /**
431     * Sets the password for the account. The password may be null. If the account does not exist
432     * then this call has no affect.
433     * <p>
434     * It is safe to call this method from the main thread.
435     * <p>
436     * Requires that the caller has permission
437     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
438     * with the same UID as the Authenticator for the account.
439     * @param account the account whose password is to be set. Must not be null.
440     * @param password the password to set for the account. May be null.
441     */
442    public void setPassword(final Account account, final String password) {
443        if (account == null) {
444            Log.e(TAG, "the account must not be null");
445            return;
446        }
447        try {
448            mService.setPassword(account, password);
449        } catch (RemoteException e) {
450            // won't ever happen
451            throw new RuntimeException(e);
452        }
453    }
454
455    /**
456     * Sets the password for account to null. If the account does not exist then this call
457     * has no effect.
458     * <p>
459     * It is safe to call this method from the main thread.
460     * <p>
461     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
462     * @param account the account whose password is to be cleared. Must not be null.
463     */
464    public void clearPassword(final Account account) {
465        if (account == null) {
466            Log.e(TAG, "the account must not be null");
467            return;
468        }
469        try {
470            mService.clearPassword(account);
471        } catch (RemoteException e) {
472            // won't ever happen
473            throw new RuntimeException(e);
474        }
475    }
476
477    /**
478     * Sets account's userdata named "key" to the specified value. If the account does not
479     * exist then this call has no effect.
480     * <p>
481     * It is safe to call this method from the main thread.
482     * <p>
483     * Requires that the caller has permission
484     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
485     * with the same UID as the Authenticator for the account.
486     * @param account the account whose userdata is to be set. Must not be null.
487     * @param key the key of the userdata to set. Must not be null.
488     * @param value the value to set. May be null.
489     */
490    public void setUserData(final Account account, final String key, final String value) {
491        if (account == null) {
492            Log.e(TAG, "the account must not be null");
493            return;
494        }
495        if (key == null) {
496            Log.e(TAG, "the key must not be null");
497            return;
498        }
499        try {
500            mService.setUserData(account, key, value);
501        } catch (RemoteException e) {
502            // won't ever happen
503            throw new RuntimeException(e);
504        }
505    }
506
507    /**
508     * Sets the authtoken named by "authTokenType" to the value specified by authToken.
509     * If the account does not exist then this call has no effect.
510     * <p>
511     * It is safe to call this method from the main thread.
512     * <p>
513     * Requires that the caller has permission
514     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
515     * with the same UID as the Authenticator for the account.
516     * @param account the account whose authtoken is to be set. Must not be null.
517     * @param authTokenType the type of the authtoken to set. Must not be null.
518     * @param authToken the authToken to set. May be null.
519     */
520    public void setAuthToken(Account account, final String authTokenType, final String authToken) {
521        try {
522            mService.setAuthToken(account, authTokenType, authToken);
523        } catch (RemoteException e) {
524            // won't ever happen
525            throw new RuntimeException(e);
526        }
527    }
528
529    /**
530     * Convenience method that makes a blocking call to
531     * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
532     * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
533     * <p>
534     * It is not safe to call this method from the main thread. See {@link #getAuthToken}.
535     * <p>
536     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
537     * @param account the account whose authtoken is to be retrieved, must not be null
538     * @param authTokenType the type of authtoken to retrieve
539     * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
540     * for the account if no authtoken is cached by the AccountManager and the the authenticator
541     * does not have valid credentials to get an authtoken.
542     * @return an authtoken for the given account and authTokenType, if one is cached by the
543     * AccountManager, null otherwise.
544     * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
545     * an invalid response.
546     * @throws OperationCanceledException if the request is canceled for any reason
547     * @throws java.io.IOException if the authenticator experiences an IOException while attempting
548     * to communicate with its backend server.
549     */
550    public String blockingGetAuthToken(Account account, String authTokenType,
551            boolean notifyAuthFailure)
552            throws OperationCanceledException, IOException, AuthenticatorException {
553        Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
554                null /* handler */).getResult();
555        return bundle.getString(KEY_AUTHTOKEN);
556    }
557
558    /**
559     * Request that an authtoken of the specified type be returned for an account.
560     * If the Account Manager has a cached authtoken of the requested type then it will
561     * service the request itself. Otherwise it will pass the request on to the authenticator.
562     * The authenticator can try to service this request with information it already has stored
563     * in the AccountManager but may need to launch an activity to prompt the
564     * user to enter credentials. If it is able to retrieve the authtoken it will be returned
565     * in the result.
566     * <p>
567     * If the authenticator needs to prompt the user for credentials it will return an intent to
568     * an activity that will do the prompting. The supplied activity will be used to launch the
569     * intent and the result will come from the launched activity.
570     * <p>
571     * This call returns immediately but runs asynchronously and the result is accessed via the
572     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
573     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
574     * method asynchronously then they will generally pass in a callback object that will get
575     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
576     * they will generally pass null for the callback and instead call
577     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
578     * which will then block until the request completes.
579     * <p>
580     * Do not block the main thread waiting this method's result.
581     * <p>
582     * Not allowed from main thread (but allowed from other threads):
583     * <pre>
584     * Bundle result = getAuthToken(
585     *   account, authTokenType, options, activity, callback, handler).getResult();
586     * </pre>
587     * Allowed from main thread:
588     * <pre>
589     * getAuthToken(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() {
590     *    public void run(AccountManagerFuture<Bundle> future) {
591     *        Bundle result = future.getResult();
592     *        // use result
593     *    }
594     * }, handler);
595     * </pre>
596     * <p>
597     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
598     *
599     * @param account The account whose credentials are to be updated.
600     * @param authTokenType the auth token to retrieve as part of updating the credentials.
601     * May be null.
602     * @param options authenticator specific options for the request
603     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
604     * the intent will be started with this activity. If you do not with to have the intent
605     * started automatically then use the other form,
606     * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, android.os.Handler)}
607     * @param callback A callback to invoke when the request completes. If null then
608     * no callback is invoked.
609     * @param handler The {@link Handler} to use to invoke the callback. If null then the
610     * main thread's {@link Handler} is used.
611     * @return an {@link AccountManagerFuture} that represents the future result of the call.
612     * The future result is a {@link Bundle} that contains:
613     * <ul>
614     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
615     * </ul>
616     * If the user presses "back" then the request will be canceled.
617     */
618    public AccountManagerFuture<Bundle> getAuthToken(
619            final Account account, final String authTokenType, final Bundle options,
620            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
621        if (activity == null) throw new IllegalArgumentException("activity is null");
622        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
623        return new AmsTask(activity, handler, callback) {
624            public void doWork() throws RemoteException {
625                mService.getAuthToken(mResponse, account, authTokenType,
626                        false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
627                        options);
628            }
629        }.start();
630    }
631
632    /**
633     * Request that an authtoken of the specified type be returned for an account.
634     * If the Account Manager has a cached authtoken of the requested type then it will
635     * service the request itself. Otherwise it will pass the request on to the authenticator.
636     * The authenticator can try to service this request with information it already has stored
637     * in the AccountManager but may need to launch an activity to prompt the
638     * user to enter credentials. If it is able to retrieve the authtoken it will be returned
639     * in the result.
640     * <p>
641     * If the authenticator needs to prompt the user for credentials, rather than returning the
642     * authtoken it will instead return an intent for
643     * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
644     * is true then a notification will be created that launches this intent. This intent can be
645     * invoked by the caller directly to start the activity that prompts the user for the
646     * updated credentials. Otherwise this activity will not be run until the user activates
647     * the notification.
648     * <p>
649     * This call returns immediately but runs asynchronously and the result is accessed via the
650     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
651     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
652     * method asynchronously then they will generally pass in a callback object that will get
653     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
654     * they will generally pass null for the callback and instead call
655     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
656     * which will then block until the request completes.
657     * <p>
658     * Do not block the main thread waiting this method's result.
659     * <p>
660     * Not allowed from main thread (but allowed from other threads):
661     * <pre>
662     * Bundle result = getAuthToken(
663     *   account, authTokenType, notifyAuthFailure, callback, handler).getResult();
664     * </pre>
665     * Allowed from main thread:
666     * <pre>
667     * getAuthToken(account, authTokenType, notifyAuthFailure, new AccountManagerCallback<Bundle>() {
668     *    public void run(AccountManagerFuture<Bundle> future) {
669     *        Bundle result = future.getResult();
670     *        // use result
671     *    }
672     * }, handler);
673     * </pre>
674     * <p>
675     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
676     *
677     * @param account The account whose credentials are to be updated.
678     * @param authTokenType the auth token to retrieve as part of updating the credentials.
679     * May be null.
680     * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
681     * result then a "sign-on needed" notification will be created that will launch this intent.
682     * @param callback A callback to invoke when the request completes. If null then
683     * no callback is invoked.
684     * @param handler The {@link Handler} to use to invoke the callback. If null then the
685     * main thread's {@link Handler} is used.
686     * @return an {@link AccountManagerFuture} that represents the future result of the call.
687     * The future result is a {@link Bundle} that contains either:
688     * <ul>
689     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
690     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
691     * if the authenticator is able to retrieve the auth token
692     * </ul>
693     * If the user presses "back" then the request will be canceled.
694     */
695    public AccountManagerFuture<Bundle> getAuthToken(
696            final Account account, final String authTokenType, final boolean notifyAuthFailure,
697            AccountManagerCallback<Bundle> callback, Handler handler) {
698        if (account == null) throw new IllegalArgumentException("account is null");
699        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
700        return new AmsTask(null, handler, callback) {
701            public void doWork() throws RemoteException {
702                mService.getAuthToken(mResponse, account, authTokenType,
703                        notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
704            }
705        }.start();
706    }
707
708    /**
709     * Request that an account be added with the given accountType. This request
710     * is processed by the authenticator for the account type. If no authenticator is registered
711     * in the system then {@link AuthenticatorException} is thrown.
712     * <p>
713     * This call returns immediately but runs asynchronously and the result is accessed via the
714     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
715     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
716     * method asynchronously then they will generally pass in a callback object that will get
717     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
718     * they will generally pass null for the callback and instead call
719     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
720     * which will then block until the request completes.
721     * <p>
722     * Do not block the main thread waiting this method's result.
723     * <p>
724     * Not allowed from main thread (but allowed from other threads):
725     * <pre>
726     * Bundle result = addAccount(
727     *   account, authTokenType, features, options, activity, callback, handler).getResult();
728     * </pre>
729     * Allowed from main thread:
730     * <pre>
731     * addAccount(account, authTokenType, features, options, activity, new AccountManagerCallback<Bundle>() {
732     *    public void run(AccountManagerFuture<Bundle> future) {
733     *        Bundle result = future.getResult();
734     *        // use result
735     *    }
736     * }, handler);
737     * </pre>
738     * <p>
739     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
740     *
741     * @param accountType The type of account to add. This must not be null.
742     * @param authTokenType The account that is added should be able to service this auth token
743     * type. This may be null.
744     * @param requiredFeatures The account that is added should support these features.
745     * This array may be null or empty.
746     * @param addAccountOptions A bundle of authenticator-specific options that is passed on
747     * to the authenticator. This may be null.
748     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
749     * the intent will be started with this activity. If activity is null then the result will
750     * be returned as-is.
751     * @param callback A callback to invoke when the request completes. If null then
752     * no callback is invoked.
753     * @param handler The {@link Handler} to use to invoke the callback. If null then the
754     * main thread's {@link Handler} is used.
755     * @return an {@link AccountManagerFuture} that represents the future result of the call.
756     * The future result is a {@link Bundle} that contains either:
757     * <ul>
758     * <li> {@link #KEY_INTENT}, or
759     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
760     * </ul>
761     */
762    public AccountManagerFuture<Bundle> addAccount(final String accountType,
763            final String authTokenType, final String[] requiredFeatures,
764            final Bundle addAccountOptions,
765            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
766        return new AmsTask(activity, handler, callback) {
767            public void doWork() throws RemoteException {
768                if (accountType == null) {
769                    Log.e(TAG, "the account must not be null");
770                    // to unblock caller waiting on Future.get()
771                    set(new Bundle());
772                    return;
773                }
774                mService.addAcount(mResponse, accountType, authTokenType,
775                        requiredFeatures, activity != null, addAccountOptions);
776            }
777        }.start();
778    }
779
780    /**
781     * Queries for accounts that match the given account type and feature set.
782     * <p>
783     * This call returns immediately but runs asynchronously and the result is accessed via the
784     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
785     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
786     * method asynchronously then they will generally pass in a callback object that will get
787     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
788     * they will generally pass null for the callback and instead call
789     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
790     * which will then block until the request completes.
791     * <p>
792     * Do not block the main thread waiting this method's result.
793     * <p>
794     * Not allowed from main thread (but allowed from other threads):
795     * <pre>
796     * Account[] result =
797     *   getAccountsByTypeAndFeatures(accountType, features, callback, handler).getResult();
798     * </pre>
799     * Allowed from main thread:
800     * <pre>
801     * getAccountsByTypeAndFeatures(accountType, features, new AccountManagerCallback<Account[]>() {
802     *    public void run(AccountManagerFuture<Account[]> future) {
803     *         Account[] result = future.getResult();
804     *        // use result
805     *    }
806     * }, handler);
807     * </pre>
808     * <p>
809     * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
810     *
811     * @param type The type of {@link Account} to return. If null is passed in then an empty
812     * array will be returned.
813     * @param features the features with which to filter the accounts list. Each returned account
814     * will have all specified features. This may be null, which will mean the account list will
815     * not be filtered by features, making this functionally identical to
816     * {@link #getAccountsByType(String)}.
817     * @param callback A callback to invoke when the request completes. If null then
818     * no callback is invoked.
819     * @param handler The {@link Handler} to use to invoke the callback. If null then the
820     * main thread's {@link Handler} is used.
821     * @return an {@link AccountManagerFuture} that represents the future result of the call.
822     * The future result is a an {@link Account} array that contains accounts of the specified
823     * type that match all the requested features.
824     */
825    public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
826            final String type, final String[] features,
827            AccountManagerCallback<Account[]> callback, Handler handler) {
828        return new Future2Task<Account[]>(handler, callback) {
829            public void doWork() throws RemoteException {
830                if (type == null) {
831                    Log.e(TAG, "Type is null");
832                    set(new Account[0]);
833                    return;
834                }
835                mService.getAccountsByFeatures(mResponse, type, features);
836            }
837            public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
838                if (!bundle.containsKey(KEY_ACCOUNTS)) {
839                    throw new AuthenticatorException("no result in response");
840                }
841                final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
842                Account[] descs = new Account[parcelables.length];
843                for (int i = 0; i < parcelables.length; i++) {
844                    descs[i] = (Account) parcelables[i];
845                }
846                return descs;
847            }
848        }.start();
849    }
850
851    /**
852     * Requests that the authenticator checks that the user knows the credentials for the account.
853     * This is typically done by returning an intent to an activity that prompts the user to
854     * enter the credentials. This request
855     * is processed by the authenticator for the account. If no matching authenticator is
856     * registered in the system then {@link AuthenticatorException} is thrown.
857     * <p>
858     * This call returns immediately but runs asynchronously and the result is accessed via the
859     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
860     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
861     * method asynchronously then they will generally pass in a callback object that will get
862     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
863     * they will generally pass null for the callback and instead call
864     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
865     * which will then block until the request completes.
866     * <p>
867     * Do not block the main thread waiting this method's result.
868     * <p>
869     * Not allowed from main thread (but allowed from other threads):
870     * <pre>
871     * Bundle result = confirmCredentials(
872     *   account, options, activity, callback, handler).getResult();
873     * </pre>
874     * Allowed from main thread:
875     * <pre>
876     * confirmCredentials(account, options, activity, new AccountManagerCallback<Bundle>() {
877     *    public void run(AccountManagerFuture<Bundle> future) {
878     *        Bundle result = future.getResult();
879     *        // use result
880     *    }
881     * }, handler);
882     * </pre>
883     * <p>
884     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
885     *
886     * @param account The account whose credentials are to be checked
887     * @param options authenticator specific options for the request
888     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
889     * the intent will be started with this activity. If activity is null then the result will
890     * be returned as-is.
891     * @param callback A callback to invoke when the request completes. If null then
892     * no callback is invoked.
893     * @param handler The {@link Handler} to use to invoke the callback. If null then the
894     * main thread's {@link Handler} is used.
895     * @return an {@link AccountManagerFuture} that represents the future result of the call.
896     * The future result is a {@link Bundle} that contains either:
897     * <ul>
898     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
899     * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
900     * credentials
901     * </ul>
902     * If the user presses "back" then the request will be canceled.
903     */
904    public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
905            final Bundle options,
906            final Activity activity,
907            final AccountManagerCallback<Bundle> callback,
908            final Handler handler) {
909        return new AmsTask(activity, handler, callback) {
910            public void doWork() throws RemoteException {
911                mService.confirmCredentials(mResponse, account, options, activity != null);
912            }
913        }.start();
914    }
915
916    /**
917     * Requests that the authenticator update the the credentials for a user. This is typically
918     * done by returning an intent to an activity that will prompt the user to update the stored
919     * credentials for the account. This request
920     * is processed by the authenticator for the account. If no matching authenticator is
921     * registered in the system then {@link AuthenticatorException} is thrown.
922     * <p>
923     * This call returns immediately but runs asynchronously and the result is accessed via the
924     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
925     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
926     * method asynchronously then they will generally pass in a callback object that will get
927     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
928     * they will generally pass null for the callback and instead call
929     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
930     * which will then block until the request completes.
931     * <p>
932     * Do not block the main thread waiting this method's result.
933     * <p>
934     * Not allowed from main thread (but allowed from other threads):
935     * <pre>
936     * Bundle result = updateCredentials(
937     *   account, authTokenType, options, activity, callback, handler).getResult();
938     * </pre>
939     * Allowed from main thread:
940     * <pre>
941     * updateCredentials(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() {
942     *    public void run(AccountManagerFuture<Bundle> future) {
943     *        Bundle result = future.getResult();
944     *        // use result
945     *    }
946     * }, handler);
947     * </pre>
948     * <p>
949     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
950     *
951     * @param account The account whose credentials are to be updated.
952     * @param authTokenType the auth token to retrieve as part of updating the credentials.
953     * May be null.
954     * @param options authenticator specific options for the request
955     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
956     * the intent will be started with this activity. If activity is null then the result will
957     * be returned as-is.
958     * @param callback A callback to invoke when the request completes. If null then
959     * no callback is invoked.
960     * @param handler The {@link Handler} to use to invoke the callback. If null then the
961     * main thread's {@link Handler} is used.
962     * @return an {@link AccountManagerFuture} that represents the future result of the call.
963     * The future result is a {@link Bundle} that contains either:
964     * <ul>
965     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
966     * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
967     * credentials.
968     * </ul>
969     * If the user presses "back" then the request will be canceled.
970     */
971    public AccountManagerFuture<Bundle> updateCredentials(final Account account,
972            final String authTokenType,
973            final Bundle options, final Activity activity,
974            final AccountManagerCallback<Bundle> callback,
975            final Handler handler) {
976        return new AmsTask(activity, handler, callback) {
977            public void doWork() throws RemoteException {
978                mService.updateCredentials(mResponse, account, authTokenType, activity != null,
979                        options);
980            }
981        }.start();
982    }
983
984    /**
985     * Request that the properties for an authenticator be updated. This is typically done by
986     * returning an intent to an activity that will allow the user to make changes. This request
987     * is processed by the authenticator for the account. If no matching authenticator is
988     * registered in the system then {@link AuthenticatorException} is thrown.
989     * <p>
990     * This call returns immediately but runs asynchronously and the result is accessed via the
991     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
992     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
993     * method asynchronously then they will generally pass in a callback object that will get
994     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
995     * they will generally pass null for the callback and instead call
996     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
997     * which will then block until the request completes.
998     * <p>
999     * Do not block the main thread waiting this method's result.
1000     * <p>
1001     * Not allowed from main thread (but allowed from other threads):
1002     * <pre>
1003     * Bundle result = editProperties(accountType, activity, callback, handler).getResult();
1004     * </pre>
1005     * Allowed from main thread:
1006     * <pre>
1007     * editProperties(accountType, activity, new AccountManagerCallback<Bundle>() {
1008     *    public void run(AccountManagerFuture<Bundle> future) {
1009     *        Bundle result = future.getResult();
1010     *        // use result
1011     *    }
1012     * }, handler);
1013     * </pre>
1014     * <p>
1015     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
1016     *
1017     * @param accountType The account type of the authenticator whose properties are to be edited.
1018     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
1019     * the intent will be started with this activity. If activity is null then the result will
1020     * be returned as-is.
1021     * @param callback A callback to invoke when the request completes. If null then
1022     * no callback is invoked.
1023     * @param handler The {@link Handler} to use to invoke the callback. If null then the
1024     * main thread's {@link Handler} is used.
1025     * @return an {@link AccountManagerFuture} that represents the future result of the call.
1026     * The future result is a {@link Bundle} that contains either:
1027     * <ul>
1028     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
1029     * <li> nothing, returned if the edit completes successfully
1030     * </ul>
1031     * If the user presses "back" then the request will be canceled.
1032     */
1033    public AccountManagerFuture<Bundle> editProperties(final String accountType,
1034            final Activity activity, final AccountManagerCallback<Bundle> callback,
1035            final Handler handler) {
1036        return new AmsTask(activity, handler, callback) {
1037            public void doWork() throws RemoteException {
1038                mService.editProperties(mResponse, accountType, activity != null);
1039            }
1040        }.start();
1041    }
1042
1043    private void ensureNotOnMainThread() {
1044        final Looper looper = Looper.myLooper();
1045        if (looper != null && looper == mContext.getMainLooper()) {
1046            final IllegalStateException exception = new IllegalStateException(
1047                    "calling this from your main thread can lead to deadlock");
1048            Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
1049                    exception);
1050            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
1051                throw exception;
1052            }
1053        }
1054    }
1055
1056    private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
1057            final AccountManagerFuture<Bundle> future) {
1058        handler = handler == null ? mMainHandler : handler;
1059        handler.post(new Runnable() {
1060            public void run() {
1061                callback.run(future);
1062            }
1063        });
1064    }
1065
1066    private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
1067            final Account[] accounts) {
1068        final Account[] accountsCopy = new Account[accounts.length];
1069        // send a copy to make sure that one doesn't
1070        // change what another sees
1071        System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
1072        handler = (handler == null) ? mMainHandler : handler;
1073        handler.post(new Runnable() {
1074            public void run() {
1075                try {
1076                    listener.onAccountsUpdated(accountsCopy);
1077                } catch (SQLException e) {
1078                    // Better luck next time.  If the problem was disk-full,
1079                    // the STORAGE_OK intent will re-trigger the update.
1080                    Log.e(TAG, "Can't update accounts", e);
1081                }
1082            }
1083        });
1084    }
1085
1086    private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
1087        final IAccountManagerResponse mResponse;
1088        final Handler mHandler;
1089        final AccountManagerCallback<Bundle> mCallback;
1090        final Activity mActivity;
1091        public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
1092            super(new Callable<Bundle>() {
1093                public Bundle call() throws Exception {
1094                    throw new IllegalStateException("this should never be called");
1095                }
1096            });
1097
1098            mHandler = handler;
1099            mCallback = callback;
1100            mActivity = activity;
1101            mResponse = new Response();
1102        }
1103
1104        public final AccountManagerFuture<Bundle> start() {
1105            try {
1106                doWork();
1107            } catch (RemoteException e) {
1108                setException(e);
1109            }
1110            return this;
1111        }
1112
1113        public abstract void doWork() throws RemoteException;
1114
1115        private Bundle internalGetResult(Long timeout, TimeUnit unit)
1116                throws OperationCanceledException, IOException, AuthenticatorException {
1117            if (!isDone()) {
1118                ensureNotOnMainThread();
1119            }
1120            try {
1121                if (timeout == null) {
1122                    return get();
1123                } else {
1124                    return get(timeout, unit);
1125                }
1126            } catch (CancellationException e) {
1127                throw new OperationCanceledException();
1128            } catch (TimeoutException e) {
1129                // fall through and cancel
1130            } catch (InterruptedException e) {
1131                // fall through and cancel
1132            } catch (ExecutionException e) {
1133                final Throwable cause = e.getCause();
1134                if (cause instanceof IOException) {
1135                    throw (IOException) cause;
1136                } else if (cause instanceof UnsupportedOperationException) {
1137                    throw new AuthenticatorException(cause);
1138                } else if (cause instanceof AuthenticatorException) {
1139                    throw (AuthenticatorException) cause;
1140                } else if (cause instanceof RuntimeException) {
1141                    throw (RuntimeException) cause;
1142                } else if (cause instanceof Error) {
1143                    throw (Error) cause;
1144                } else {
1145                    throw new IllegalStateException(cause);
1146                }
1147            } finally {
1148                cancel(true /* interrupt if running */);
1149            }
1150            throw new OperationCanceledException();
1151        }
1152
1153        public Bundle getResult()
1154                throws OperationCanceledException, IOException, AuthenticatorException {
1155            return internalGetResult(null, null);
1156        }
1157
1158        public Bundle getResult(long timeout, TimeUnit unit)
1159                throws OperationCanceledException, IOException, AuthenticatorException {
1160            return internalGetResult(timeout, unit);
1161        }
1162
1163        protected void done() {
1164            if (mCallback != null) {
1165                postToHandler(mHandler, mCallback, this);
1166            }
1167        }
1168
1169        /** Handles the responses from the AccountManager */
1170        private class Response extends IAccountManagerResponse.Stub {
1171            public void onResult(Bundle bundle) {
1172                Intent intent = bundle.getParcelable("intent");
1173                if (intent != null && mActivity != null) {
1174                    // since the user provided an Activity we will silently start intents
1175                    // that we see
1176                    mActivity.startActivity(intent);
1177                    // leave the Future running to wait for the real response to this request
1178                } else if (bundle.getBoolean("retry")) {
1179                    try {
1180                        doWork();
1181                    } catch (RemoteException e) {
1182                        // this will only happen if the system process is dead, which means
1183                        // we will be dying ourselves
1184                    }
1185                } else {
1186                    set(bundle);
1187                }
1188            }
1189
1190            public void onError(int code, String message) {
1191                if (code == ERROR_CODE_CANCELED) {
1192                    // the authenticator indicated that this request was canceled, do so now
1193                    cancel(true /* mayInterruptIfRunning */);
1194                    return;
1195                }
1196                setException(convertErrorToException(code, message));
1197            }
1198        }
1199
1200    }
1201
1202    private abstract class BaseFutureTask<T> extends FutureTask<T> {
1203        final public IAccountManagerResponse mResponse;
1204        final Handler mHandler;
1205
1206        public BaseFutureTask(Handler handler) {
1207            super(new Callable<T>() {
1208                public T call() throws Exception {
1209                    throw new IllegalStateException("this should never be called");
1210                }
1211            });
1212            mHandler = handler;
1213            mResponse = new Response();
1214        }
1215
1216        public abstract void doWork() throws RemoteException;
1217
1218        public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
1219
1220        protected void postRunnableToHandler(Runnable runnable) {
1221            Handler handler = (mHandler == null) ? mMainHandler : mHandler;
1222            handler.post(runnable);
1223        }
1224
1225        protected void startTask() {
1226            try {
1227                doWork();
1228            } catch (RemoteException e) {
1229                setException(e);
1230            }
1231        }
1232
1233        protected class Response extends IAccountManagerResponse.Stub {
1234            public void onResult(Bundle bundle) {
1235                try {
1236                    T result = bundleToResult(bundle);
1237                    if (result == null) {
1238                        return;
1239                    }
1240                    set(result);
1241                    return;
1242                } catch (ClassCastException e) {
1243                    // we will set the exception below
1244                } catch (AuthenticatorException e) {
1245                    // we will set the exception below
1246                }
1247                onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
1248            }
1249
1250            public void onError(int code, String message) {
1251                if (code == ERROR_CODE_CANCELED) {
1252                    cancel(true /* mayInterruptIfRunning */);
1253                    return;
1254                }
1255                setException(convertErrorToException(code, message));
1256            }
1257        }
1258    }
1259
1260    private abstract class Future2Task<T>
1261            extends BaseFutureTask<T> implements AccountManagerFuture<T> {
1262        final AccountManagerCallback<T> mCallback;
1263        public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
1264            super(handler);
1265            mCallback = callback;
1266        }
1267
1268        protected void done() {
1269            if (mCallback != null) {
1270                postRunnableToHandler(new Runnable() {
1271                    public void run() {
1272                        mCallback.run(Future2Task.this);
1273                    }
1274                });
1275            }
1276        }
1277
1278        public Future2Task<T> start() {
1279            startTask();
1280            return this;
1281        }
1282
1283        private T internalGetResult(Long timeout, TimeUnit unit)
1284                throws OperationCanceledException, IOException, AuthenticatorException {
1285            if (!isDone()) {
1286                ensureNotOnMainThread();
1287            }
1288            try {
1289                if (timeout == null) {
1290                    return get();
1291                } else {
1292                    return get(timeout, unit);
1293                }
1294            } catch (InterruptedException e) {
1295                // fall through and cancel
1296            } catch (TimeoutException e) {
1297                // fall through and cancel
1298            } catch (CancellationException e) {
1299                // fall through and cancel
1300            } catch (ExecutionException e) {
1301                final Throwable cause = e.getCause();
1302                if (cause instanceof IOException) {
1303                    throw (IOException) cause;
1304                } else if (cause instanceof UnsupportedOperationException) {
1305                    throw new AuthenticatorException(cause);
1306                } else if (cause instanceof AuthenticatorException) {
1307                    throw (AuthenticatorException) cause;
1308                } else if (cause instanceof RuntimeException) {
1309                    throw (RuntimeException) cause;
1310                } else if (cause instanceof Error) {
1311                    throw (Error) cause;
1312                } else {
1313                    throw new IllegalStateException(cause);
1314                }
1315            } finally {
1316                cancel(true /* interrupt if running */);
1317            }
1318            throw new OperationCanceledException();
1319        }
1320
1321        public T getResult()
1322                throws OperationCanceledException, IOException, AuthenticatorException {
1323            return internalGetResult(null, null);
1324        }
1325
1326        public T getResult(long timeout, TimeUnit unit)
1327                throws OperationCanceledException, IOException, AuthenticatorException {
1328            return internalGetResult(timeout, unit);
1329        }
1330
1331    }
1332
1333    private Exception convertErrorToException(int code, String message) {
1334        if (code == ERROR_CODE_NETWORK_ERROR) {
1335            return new IOException(message);
1336        }
1337
1338        if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
1339            return new UnsupportedOperationException(message);
1340        }
1341
1342        if (code == ERROR_CODE_INVALID_RESPONSE) {
1343            return new AuthenticatorException(message);
1344        }
1345
1346        if (code == ERROR_CODE_BAD_ARGUMENTS) {
1347            return new IllegalArgumentException(message);
1348        }
1349
1350        return new AuthenticatorException(message);
1351    }
1352
1353    private class GetAuthTokenByTypeAndFeaturesTask
1354            extends AmsTask implements AccountManagerCallback<Bundle> {
1355        GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
1356                final String[] features, Activity activityForPrompting,
1357                final Bundle addAccountOptions, final Bundle loginOptions,
1358                AccountManagerCallback<Bundle> callback, Handler handler) {
1359            super(activityForPrompting, handler, callback);
1360            if (accountType == null) throw new IllegalArgumentException("account type is null");
1361            mAccountType = accountType;
1362            mAuthTokenType = authTokenType;
1363            mFeatures = features;
1364            mAddAccountOptions = addAccountOptions;
1365            mLoginOptions = loginOptions;
1366            mMyCallback = this;
1367        }
1368        volatile AccountManagerFuture<Bundle> mFuture = null;
1369        final String mAccountType;
1370        final String mAuthTokenType;
1371        final String[] mFeatures;
1372        final Bundle mAddAccountOptions;
1373        final Bundle mLoginOptions;
1374        final AccountManagerCallback<Bundle> mMyCallback;
1375
1376        public void doWork() throws RemoteException {
1377            getAccountsByTypeAndFeatures(mAccountType, mFeatures,
1378                    new AccountManagerCallback<Account[]>() {
1379                        public void run(AccountManagerFuture<Account[]> future) {
1380                            Account[] accounts;
1381                            try {
1382                                accounts = future.getResult();
1383                            } catch (OperationCanceledException e) {
1384                                setException(e);
1385                                return;
1386                            } catch (IOException e) {
1387                                setException(e);
1388                                return;
1389                            } catch (AuthenticatorException e) {
1390                                setException(e);
1391                                return;
1392                            }
1393
1394                            if (accounts.length == 0) {
1395                                if (mActivity != null) {
1396                                    // no accounts, add one now. pretend that the user directly
1397                                    // made this request
1398                                    mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
1399                                            mAddAccountOptions, mActivity, mMyCallback, mHandler);
1400                                } else {
1401                                    // send result since we can't prompt to add an account
1402                                    Bundle result = new Bundle();
1403                                    result.putString(KEY_ACCOUNT_NAME, null);
1404                                    result.putString(KEY_ACCOUNT_TYPE, null);
1405                                    result.putString(KEY_AUTHTOKEN, null);
1406                                    try {
1407                                        mResponse.onResult(result);
1408                                    } catch (RemoteException e) {
1409                                        // this will never happen
1410                                    }
1411                                    // we are done
1412                                }
1413                            } else if (accounts.length == 1) {
1414                                // have a single account, return an authtoken for it
1415                                if (mActivity == null) {
1416                                    mFuture = getAuthToken(accounts[0], mAuthTokenType,
1417                                            false /* notifyAuthFailure */, mMyCallback, mHandler);
1418                                } else {
1419                                    mFuture = getAuthToken(accounts[0],
1420                                            mAuthTokenType, mLoginOptions,
1421                                            mActivity, mMyCallback, mHandler);
1422                                }
1423                            } else {
1424                                if (mActivity != null) {
1425                                    IAccountManagerResponse chooseResponse =
1426                                            new IAccountManagerResponse.Stub() {
1427                                        public void onResult(Bundle value) throws RemoteException {
1428                                            Account account = new Account(
1429                                                    value.getString(KEY_ACCOUNT_NAME),
1430                                                    value.getString(KEY_ACCOUNT_TYPE));
1431                                            mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
1432                                                    mActivity, mMyCallback, mHandler);
1433                                        }
1434
1435                                        public void onError(int errorCode, String errorMessage)
1436                                                throws RemoteException {
1437                                            mResponse.onError(errorCode, errorMessage);
1438                                        }
1439                                    };
1440                                    // have many accounts, launch the chooser
1441                                    Intent intent = new Intent();
1442                                    intent.setClassName("android",
1443                                            "android.accounts.ChooseAccountActivity");
1444                                    intent.putExtra(KEY_ACCOUNTS, accounts);
1445                                    intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
1446                                            new AccountManagerResponse(chooseResponse));
1447                                    mActivity.startActivity(intent);
1448                                    // the result will arrive via the IAccountManagerResponse
1449                                } else {
1450                                    // send result since we can't prompt to select an account
1451                                    Bundle result = new Bundle();
1452                                    result.putString(KEY_ACCOUNTS, null);
1453                                    try {
1454                                        mResponse.onResult(result);
1455                                    } catch (RemoteException e) {
1456                                        // this will never happen
1457                                    }
1458                                    // we are done
1459                                }
1460                            }
1461                        }}, mHandler);
1462        }
1463
1464        public void run(AccountManagerFuture<Bundle> future) {
1465            try {
1466                set(future.getResult());
1467            } catch (OperationCanceledException e) {
1468                cancel(true /* mayInterruptIfRUnning */);
1469            } catch (IOException e) {
1470                setException(e);
1471            } catch (AuthenticatorException e) {
1472                setException(e);
1473            }
1474        }
1475    }
1476
1477    /**
1478     * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
1479     * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
1480     * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
1481     * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
1482     * feature set, and addAccountOptions. If there is exactly one then
1483     * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
1484     * called with that account. If there are more than one then a chooser activity is launched
1485     * to prompt the user to select one of them and then the authtoken is retrieved for it,
1486     * <p>
1487     * This call returns immediately but runs asynchronously and the result is accessed via the
1488     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
1489     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
1490     * method asynchronously then they will generally pass in a callback object that will get
1491     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
1492     * they will generally pass null for the callback and instead call
1493     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
1494     * which will then block until the request completes.
1495     * <p>
1496     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
1497     *
1498     * @param accountType the accountType to query; this must be non-null
1499     * @param authTokenType the type of authtoken to retrieve; this must be non-null
1500     * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
1501     * @param activityForPrompting The activity used to start any account management
1502     * activities that are required to fulfill this request. This may be null.
1503     * @param addAccountOptions authenticator-specific options used if an account needs to be added
1504     * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
1505     * @param callback A callback to invoke when the request completes. If null then
1506     * no callback is invoked.
1507     * @param handler The {@link Handler} to use to invoke the callback. If null then the
1508     * main thread's {@link Handler} is used.
1509     * @return an {@link AccountManagerFuture} that represents the future result of the call.
1510     * The future result is a {@link Bundle} that contains either:
1511     * <ul>
1512     * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
1513     * fulfill the request.
1514     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
1515     * request completes successfully.
1516     * </ul>
1517     * If the user presses "back" then the request will be canceled.
1518     */
1519    public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
1520            final String accountType, final String authTokenType, final String[] features,
1521            final Activity activityForPrompting, final Bundle addAccountOptions,
1522            final Bundle getAuthTokenOptions,
1523            final AccountManagerCallback<Bundle> callback, final Handler handler) {
1524        if (accountType == null) throw new IllegalArgumentException("account type is null");
1525        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1526        final GetAuthTokenByTypeAndFeaturesTask task =
1527                new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
1528                activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
1529        task.start();
1530        return task;
1531    }
1532
1533    private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
1534            Maps.newHashMap();
1535
1536    /**
1537     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
1538     * so that it can read the updated list of accounts and send them to the listener
1539     * in mAccountsUpdatedListeners.
1540     */
1541    private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
1542        public void onReceive(final Context context, final Intent intent) {
1543            final Account[] accounts = getAccounts();
1544            // send the result to the listeners
1545            synchronized (mAccountsUpdatedListeners) {
1546                for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
1547                        mAccountsUpdatedListeners.entrySet()) {
1548                    postToHandler(entry.getValue(), entry.getKey(), accounts);
1549                }
1550            }
1551        }
1552    };
1553
1554    /**
1555     * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
1556     * The listener is guaranteed to be invoked on the thread of the Handler that is passed
1557     * in or the main thread's Handler if handler is null.
1558     * <p>
1559     * You must remove this listener before the context that was used to retrieve this
1560     * {@link AccountManager} instance goes away. This generally means when the Activity
1561     * or Service you are running is stopped.
1562     * @param listener the listener to add
1563     * @param handler the Handler whose thread will be used to invoke the listener. If null
1564     * the AccountManager context's main thread will be used.
1565     * @param updateImmediately if true then the listener will be invoked as a result of this
1566     * call.
1567     * @throws IllegalArgumentException if listener is null
1568     * @throws IllegalStateException if listener was already added
1569     */
1570    public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
1571            Handler handler, boolean updateImmediately) {
1572        if (listener == null) {
1573            throw new IllegalArgumentException("the listener is null");
1574        }
1575        synchronized (mAccountsUpdatedListeners) {
1576            if (mAccountsUpdatedListeners.containsKey(listener)) {
1577                throw new IllegalStateException("this listener is already added");
1578            }
1579            final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
1580
1581            mAccountsUpdatedListeners.put(listener, handler);
1582
1583            if (wasEmpty) {
1584                // Register a broadcast receiver to monitor account changes
1585                IntentFilter intentFilter = new IntentFilter();
1586                intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
1587                // To recover from disk-full.
1588                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
1589                mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1590            }
1591        }
1592
1593        if (updateImmediately) {
1594            postToHandler(handler, listener, getAccounts());
1595        }
1596    }
1597
1598    /**
1599     * Remove an {@link OnAccountsUpdateListener} that was previously registered with
1600     * {@link #addOnAccountsUpdatedListener}.
1601     * @param listener the listener to remove
1602     * @throws IllegalArgumentException if listener is null
1603     * @throws IllegalStateException if listener was not already added
1604     */
1605    public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
1606        if (listener == null) {
1607            Log.e(TAG, "Missing listener");
1608            return;
1609        }
1610        synchronized (mAccountsUpdatedListeners) {
1611            if (!mAccountsUpdatedListeners.containsKey(listener)) {
1612                Log.e(TAG, "Listener was not previously added");
1613                return;
1614            }
1615            mAccountsUpdatedListeners.remove(listener);
1616            if (mAccountsUpdatedListeners.isEmpty()) {
1617                mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1618            }
1619        }
1620    }
1621}
1622