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