AccountManager.java revision 31957f1badbb900bbfe211317e1ea992d650a72d
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 userdata 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 userdata) {
245        try {
246            return mService.addAccount(account, password, userdata);
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        if (account == null) {
324            throw new IllegalArgumentException("the account must not be null");
325        }
326        if (authTokenType == null) {
327            return null;
328        }
329        try {
330            return mService.peekAuthToken(account, authTokenType);
331        } catch (RemoteException e) {
332            // won't ever happen
333            throw new RuntimeException(e);
334        }
335    }
336
337    /**
338     * Sets the password for the account. The password may be null. If the account does not exist
339     * then this call has no affect.
340     * <p>
341     * Requires that the caller has permission
342     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
343     * with the same UID as the Authenticator for the account.
344     * @param account the account whose password is to be set. Must not be null.
345     * @param password the password to set for the account. May be null.
346     */
347    public void setPassword(final Account account, final String password) {
348        if (account == null) {
349            throw new IllegalArgumentException("the account must not be null");
350        }
351        try {
352            mService.setPassword(account, password);
353        } catch (RemoteException e) {
354            // won't ever happen
355            throw new RuntimeException(e);
356        }
357    }
358
359    /**
360     * Sets the password for account to null. If the account does not exist then this call
361     * has no effect.
362     * <p>
363     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
364     * @param account the account whose password is to be cleared. Must not be null.
365     */
366    public void clearPassword(final Account account) {
367        if (account == null) {
368            throw new IllegalArgumentException("the account must not be null");
369        }
370        try {
371            mService.clearPassword(account);
372        } catch (RemoteException e) {
373            // won't ever happen
374            throw new RuntimeException(e);
375        }
376    }
377
378    /**
379     * Sets account's userdata named "key" to the specified value. If the account does not
380     * exist then this call has no effect.
381     * <p>
382     * Requires that the caller has permission
383     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
384     * with the same UID as the Authenticator for the account.
385     * @param account the account whose userdata is to be set. Must not be null.
386     * @param key the key of the userdata to set. Must not be null.
387     * @param value the value to set. May be null.
388     */
389    public void setUserData(final Account account, final String key, final String value) {
390        if (account == null) {
391            throw new IllegalArgumentException("the account must not be null");
392        }
393        if (key == null) {
394            throw new IllegalArgumentException("the key must not be null");
395        }
396        try {
397            mService.setUserData(account, key, value);
398        } catch (RemoteException e) {
399            // won't ever happen
400            throw new RuntimeException(e);
401        }
402    }
403
404    /**
405     * Sets the authtoken named by "authTokenType" to the value specified by authToken.
406     * If the account does not exist then this call has no effect.
407     * <p>
408     * Requires that the caller has permission
409     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
410     * with the same UID as the Authenticator for the account.
411     * @param account the account whose authtoken is to be set. Must not be null.
412     * @param authTokenType the type of the authtoken to set. Must not be null.
413     * @param authToken the authToken to set. May be null.
414     */
415    public void setAuthToken(Account account, final String authTokenType, final String authToken) {
416        try {
417            mService.setAuthToken(account, authTokenType, authToken);
418        } catch (RemoteException e) {
419            // won't ever happen
420            throw new RuntimeException(e);
421        }
422    }
423
424    /**
425     * Convenience method that makes a blocking call to
426     * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
427     * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
428     * <p>
429     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
430     * @param account the account whose authtoken is to be retrieved, must not be null
431     * @param authTokenType the type of authtoken to retrieve
432     * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
433     * for the account if no authtoken is cached by the AccountManager and the the authenticator
434     * does not have valid credentials to get an authtoken.
435     * @return an authtoken for the given account and authTokenType, if one is cached by the
436     * AccountManager, null otherwise.
437     * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
438     * an invalid response.
439     * @throws OperationCanceledException if the request is canceled for any reason
440     * @throws java.io.IOException if the authenticator experiences an IOException while attempting
441     * to communicate with its backend server.
442     */
443    public String blockingGetAuthToken(Account account, String authTokenType,
444            boolean notifyAuthFailure)
445            throws OperationCanceledException, IOException, AuthenticatorException {
446        Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
447                null /* handler */).getResult();
448        return bundle.getString(KEY_AUTHTOKEN);
449    }
450
451    /**
452     * Request that an authtoken of the specified type be returned for an account.
453     * If the Account Manager has a cached authtoken of the requested type then it will
454     * service the request itself. Otherwise it will pass the request on to the authenticator.
455     * The authenticator can try to service this request with information it already has stored
456     * in the AccountManager but may need to launch an activity to prompt the
457     * user to enter credentials. If it is able to retrieve the authtoken it will be returned
458     * in the result.
459     * <p>
460     * If the authenticator needs to prompt the user for credentials it will return an intent to
461     * the activity that will do the prompting. If an activity is supplied then that activity
462     * will be used to launch the intent and the result will come from it. Otherwise a result will
463     * be returned that contains the intent.
464     * <p>
465     * This call returns immediately but runs asynchronously and the result is accessed via the
466     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
467     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
468     * method asynchronously then they will generally pass in a callback object that will get
469     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
470     * they will generally pass null for the callback and instead call
471     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
472     * which will then block until the request completes.
473     * <p>
474     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
475     *
476     * @param account The account whose credentials are to be updated.
477     * @param authTokenType the auth token to retrieve as part of updating the credentials.
478     * May be null.
479     * @param options authenticator specific options for the request
480     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
481     * the intent will be started with this activity. If activity is null then the result will
482     * be returned as-is.
483     * @param callback A callback to invoke when the request completes. If null then
484     * no callback is invoked.
485     * @param handler The {@link Handler} to use to invoke the callback. If null then the
486     * main thread's {@link Handler} is used.
487     * @return an {@link AccountManagerFuture} that represents the future result of the call.
488     * The future result is a {@link Bundle} that contains:
489     * <ul>
490     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
491     * </ul>
492     * If the user presses "back" then the request will be canceled.
493     */
494    public AccountManagerFuture<Bundle> getAuthToken(
495            final Account account, final String authTokenType, final Bundle options,
496            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
497        if (activity == null) throw new IllegalArgumentException("activity is null");
498        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
499        return new AmsTask(activity, handler, callback) {
500            public void doWork() throws RemoteException {
501                mService.getAuthToken(mResponse, account, authTokenType,
502                        false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
503                        options);
504            }
505        }.start();
506    }
507
508    /**
509     * Request that an authtoken of the specified type be returned for an account.
510     * If the Account Manager has a cached authtoken of the requested type then it will
511     * service the request itself. Otherwise it will pass the request on to the authenticator.
512     * The authenticator can try to service this request with information it already has stored
513     * in the AccountManager but may need to launch an activity to prompt the
514     * user to enter credentials. If it is able to retrieve the authtoken it will be returned
515     * in the result.
516     * <p>
517     * If the authenticator needs to prompt the user for credentials it will return an intent for
518     * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
519     * is true then a notification will be created that launches this intent.
520     * <p>
521     * This call returns immediately but runs asynchronously and the result is accessed via the
522     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
523     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
524     * method asynchronously then they will generally pass in a callback object that will get
525     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
526     * they will generally pass null for the callback and instead call
527     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
528     * which will then block until the request completes.
529     * <p>
530     * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
531     *
532     * @param account The account whose credentials are to be updated.
533     * @param authTokenType the auth token to retrieve as part of updating the credentials.
534     * May be null.
535     * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
536     * result then a "sign-on needed" notification will be created that will launch this intent.
537     * @param callback A callback to invoke when the request completes. If null then
538     * no callback is invoked.
539     * @param handler The {@link Handler} to use to invoke the callback. If null then the
540     * main thread's {@link Handler} is used.
541     * @return an {@link AccountManagerFuture} that represents the future result of the call.
542     * The future result is a {@link Bundle} that contains either:
543     * <ul>
544     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
545     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
546     * if the authenticator is able to retrieve the auth token
547     * </ul>
548     * If the user presses "back" then the request will be canceled.
549     */
550    public AccountManagerFuture<Bundle> getAuthToken(
551            final Account account, final String authTokenType, final boolean notifyAuthFailure,
552            AccountManagerCallback<Bundle> callback, Handler handler) {
553        if (account == null) throw new IllegalArgumentException("account is null");
554        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
555        return new AmsTask(null, handler, callback) {
556            public void doWork() throws RemoteException {
557                mService.getAuthToken(mResponse, account, authTokenType,
558                        notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
559            }
560        }.start();
561    }
562
563    /**
564     * Request that an account be added with the given accountType. This request
565     * is processed by the authenticator for the account type. If no authenticator is registered
566     * in the system then {@link AuthenticatorException} is thrown.
567     * <p>
568     * This call returns immediately but runs asynchronously and the result is accessed via the
569     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
570     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
571     * method asynchronously then they will generally pass in a callback object that will get
572     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
573     * they will generally pass null for the callback and instead call
574     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
575     * which will then block until the request completes.
576     * <p>
577     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
578     *
579     * @param accountType The type of account to add. This must not be null.
580     * @param authTokenType The account that is added should be able to service this auth token
581     * type. This may be null.
582     * @param requiredFeatures The account that is added should support these features.
583     * This array may be null or empty.
584     * @param addAccountOptions A bundle of authenticator-specific options that is passed on
585     * to the authenticator. This may be null.
586     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
587     * the intent will be started with this activity. If activity is null then the result will
588     * be returned as-is.
589     * @param callback A callback to invoke when the request completes. If null then
590     * no callback is invoked.
591     * @param handler The {@link Handler} to use to invoke the callback. If null then the
592     * main thread's {@link Handler} is used.
593     * @return an {@link AccountManagerFuture} that represents the future result of the call.
594     * The future result is a {@link Bundle} that contains either:
595     * <ul>
596     * <li> {@link #KEY_INTENT}, or
597     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
598     * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
599     * </ul>
600     */
601    public AccountManagerFuture<Bundle> addAccount(final String accountType,
602            final String authTokenType, final String[] requiredFeatures,
603            final Bundle addAccountOptions,
604            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
605        if (accountType == null) {
606            throw new IllegalArgumentException();
607        }
608        return new AmsTask(activity, handler, callback) {
609            public void doWork() throws RemoteException {
610                mService.addAcount(mResponse, accountType, authTokenType,
611                        requiredFeatures, activity != null, addAccountOptions);
612            }
613        }.start();
614    }
615
616    public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
617            final String type, final String[] features,
618            AccountManagerCallback<Account[]> callback, Handler handler) {
619        if (type == null) throw new IllegalArgumentException("type is null");
620        return new Future2Task<Account[]>(handler, callback) {
621            public void doWork() throws RemoteException {
622                mService.getAccountsByFeatures(mResponse, type, features);
623            }
624            public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
625                if (!bundle.containsKey(KEY_ACCOUNTS)) {
626                    throw new AuthenticatorException("no result in response");
627                }
628                final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
629                Account[] descs = new Account[parcelables.length];
630                for (int i = 0; i < parcelables.length; i++) {
631                    descs[i] = (Account) parcelables[i];
632                }
633                return descs;
634            }
635        }.start();
636    }
637
638    /**
639     * Requests that the authenticator checks that the user knows the credentials for the account.
640     * This is typically done by returning an intent to an activity that prompts the user to
641     * enter the credentials. This request
642     * is processed by the authenticator for the account. If no matching authenticator is
643     * registered in the system then {@link AuthenticatorException} is thrown.
644     * <p>
645     * This call returns immediately but runs asynchronously and the result is accessed via the
646     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
647     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
648     * method asynchronously then they will generally pass in a callback object that will get
649     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
650     * they will generally pass null for the callback and instead call
651     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
652     * which will then block until the request completes.
653     * <p>
654     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
655     *
656     * @param account The account whose credentials are to be checked
657     * @param options authenticator specific options for the request
658     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
659     * the intent will be started with this activity. If activity is null then the result will
660     * be returned as-is.
661     * @param callback A callback to invoke when the request completes. If null then
662     * no callback is invoked.
663     * @param handler The {@link Handler} to use to invoke the callback. If null then the
664     * main thread's {@link Handler} is used.
665     * @return an {@link AccountManagerFuture} that represents the future result of the call.
666     * The future result is a {@link Bundle} that contains either:
667     * <ul>
668     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
669     * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
670     * credentials
671     * </ul>
672     * If the user presses "back" then the request will be canceled.
673     */
674    public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
675            final Bundle options,
676            final Activity activity,
677            final AccountManagerCallback<Bundle> callback,
678            final Handler handler) {
679        return new AmsTask(activity, handler, callback) {
680            public void doWork() throws RemoteException {
681                mService.confirmCredentials(mResponse, account, options, activity != null);
682            }
683        }.start();
684    }
685
686    /**
687     * Requests that the authenticator update the the credentials for a user. This is typically
688     * done by returning an intent to an activity that will prompt the user to update the stored
689     * credentials for the account. This request
690     * is processed by the authenticator for the account. If no matching authenticator is
691     * registered in the system then {@link AuthenticatorException} is thrown.
692     * <p>
693     * This call returns immediately but runs asynchronously and the result is accessed via the
694     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
695     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
696     * method asynchronously then they will generally pass in a callback object that will get
697     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
698     * they will generally pass null for the callback and instead call
699     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
700     * which will then block until the request completes.
701     * <p>
702     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
703     *
704     * @param account The account whose credentials are to be updated.
705     * @param authTokenType the auth token to retrieve as part of updating the credentials.
706     * May be null.
707     * @param options authenticator specific options for the request
708     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
709     * the intent will be started with this activity. If activity is null then the result will
710     * be returned as-is.
711     * @param callback A callback to invoke when the request completes. If null then
712     * no callback is invoked.
713     * @param handler The {@link Handler} to use to invoke the callback. If null then the
714     * main thread's {@link Handler} is used.
715     * @return an {@link AccountManagerFuture} that represents the future result of the call.
716     * The future result is a {@link Bundle} that contains either:
717     * <ul>
718     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
719     * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
720     * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided.
721     * </ul>
722     * If the user presses "back" then the request will be canceled.
723     */
724    public AccountManagerFuture<Bundle> updateCredentials(final Account account,
725            final String authTokenType,
726            final Bundle options, final Activity activity,
727            final AccountManagerCallback<Bundle> callback,
728            final Handler handler) {
729        return new AmsTask(activity, handler, callback) {
730            public void doWork() throws RemoteException {
731                mService.updateCredentials(mResponse, account, authTokenType, activity != null,
732                        options);
733            }
734        }.start();
735    }
736
737    /**
738     * Request that the properties for an authenticator be updated. This is typically done by
739     * returning an intent to an activity that will allow the user to make changes. This request
740     * is processed by the authenticator for the account. If no matching authenticator is
741     * registered in the system then {@link AuthenticatorException} is thrown.
742     * <p>
743     * This call returns immediately but runs asynchronously and the result is accessed via the
744     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
745     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
746     * method asynchronously then they will generally pass in a callback object that will get
747     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
748     * they will generally pass null for the callback and instead call
749     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
750     * which will then block until the request completes.
751     * <p>
752     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
753     *
754     * @param accountType The account type of the authenticator whose properties are to be edited.
755     * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
756     * the intent will be started with this activity. If activity is null then the result will
757     * be returned as-is.
758     * @param callback A callback to invoke when the request completes. If null then
759     * no callback is invoked.
760     * @param handler The {@link Handler} to use to invoke the callback. If null then the
761     * main thread's {@link Handler} is used.
762     * @return an {@link AccountManagerFuture} that represents the future result of the call.
763     * The future result is a {@link Bundle} that contains either:
764     * <ul>
765     * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
766     * <li> nothing, returned if the edit completes successfully
767     * </ul>
768     * If the user presses "back" then the request will be canceled.
769     */
770    public AccountManagerFuture<Bundle> editProperties(final String accountType,
771            final Activity activity, final AccountManagerCallback<Bundle> callback,
772            final Handler handler) {
773        return new AmsTask(activity, handler, callback) {
774            public void doWork() throws RemoteException {
775                mService.editProperties(mResponse, accountType, activity != null);
776            }
777        }.start();
778    }
779
780    private void ensureNotOnMainThread() {
781        final Looper looper = Looper.myLooper();
782        if (looper != null && looper == mContext.getMainLooper()) {
783            // We really want to throw an exception here, but GTalkService exercises this
784            // path quite a bit and needs some serious rewrite in order to work properly.
785            //noinspection ThrowableInstanceNeverThrow
786//            Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
787//                    new Exception());
788            // TODO(fredq) remove the log and throw this exception when the callers are fixed
789//            throw new IllegalStateException(
790//                    "calling this from your main thread can lead to deadlock");
791        }
792    }
793
794    private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
795            final AccountManagerFuture<Bundle> future) {
796        handler = handler == null ? mMainHandler : handler;
797        handler.post(new Runnable() {
798            public void run() {
799                callback.run(future);
800            }
801        });
802    }
803
804    private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
805            final Account[] accounts) {
806        final Account[] accountsCopy = new Account[accounts.length];
807        // send a copy to make sure that one doesn't
808        // change what another sees
809        System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
810        handler = (handler == null) ? mMainHandler : handler;
811        handler.post(new Runnable() {
812            public void run() {
813                try {
814                    listener.onAccountsUpdated(accountsCopy);
815                } catch (SQLException e) {
816                    // Better luck next time.  If the problem was disk-full,
817                    // the STORAGE_OK intent will re-trigger the update.
818                    Log.e(TAG, "Can't update accounts", e);
819                }
820            }
821        });
822    }
823
824    private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
825        final IAccountManagerResponse mResponse;
826        final Handler mHandler;
827        final AccountManagerCallback<Bundle> mCallback;
828        final Activity mActivity;
829        public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
830            super(new Callable<Bundle>() {
831                public Bundle call() throws Exception {
832                    throw new IllegalStateException("this should never be called");
833                }
834            });
835
836            mHandler = handler;
837            mCallback = callback;
838            mActivity = activity;
839            mResponse = new Response();
840        }
841
842        public final AccountManagerFuture<Bundle> start() {
843            try {
844                doWork();
845            } catch (RemoteException e) {
846                setException(e);
847            }
848            return this;
849        }
850
851        public abstract void doWork() throws RemoteException;
852
853        private Bundle internalGetResult(Long timeout, TimeUnit unit)
854                throws OperationCanceledException, IOException, AuthenticatorException {
855            ensureNotOnMainThread();
856            try {
857                if (timeout == null) {
858                    return get();
859                } else {
860                    return get(timeout, unit);
861                }
862            } catch (CancellationException e) {
863                throw new OperationCanceledException();
864            } catch (TimeoutException e) {
865                // fall through and cancel
866            } catch (InterruptedException e) {
867                // fall through and cancel
868            } catch (ExecutionException e) {
869                final Throwable cause = e.getCause();
870                if (cause instanceof IOException) {
871                    throw (IOException) cause;
872                } else if (cause instanceof UnsupportedOperationException) {
873                    throw new AuthenticatorException(cause);
874                } else if (cause instanceof AuthenticatorException) {
875                    throw (AuthenticatorException) cause;
876                } else if (cause instanceof RuntimeException) {
877                    throw (RuntimeException) cause;
878                } else if (cause instanceof Error) {
879                    throw (Error) cause;
880                } else {
881                    throw new IllegalStateException(cause);
882                }
883            } finally {
884                cancel(true /* interrupt if running */);
885            }
886            throw new OperationCanceledException();
887        }
888
889        public Bundle getResult()
890                throws OperationCanceledException, IOException, AuthenticatorException {
891            return internalGetResult(null, null);
892        }
893
894        public Bundle getResult(long timeout, TimeUnit unit)
895                throws OperationCanceledException, IOException, AuthenticatorException {
896            return internalGetResult(timeout, unit);
897        }
898
899        protected void done() {
900            if (mCallback != null) {
901                postToHandler(mHandler, mCallback, this);
902            }
903        }
904
905        /** Handles the responses from the AccountManager */
906        private class Response extends IAccountManagerResponse.Stub {
907            public void onResult(Bundle bundle) {
908                Intent intent = bundle.getParcelable("intent");
909                if (intent != null && mActivity != null) {
910                    // since the user provided an Activity we will silently start intents
911                    // that we see
912                    mActivity.startActivity(intent);
913                    // leave the Future running to wait for the real response to this request
914                } else if (bundle.getBoolean("retry")) {
915                    try {
916                        doWork();
917                    } catch (RemoteException e) {
918                        // this will only happen if the system process is dead, which means
919                        // we will be dying ourselves
920                    }
921                } else {
922                    set(bundle);
923                }
924            }
925
926            public void onError(int code, String message) {
927                if (code == ERROR_CODE_CANCELED) {
928                    // the authenticator indicated that this request was canceled, do so now
929                    cancel(true /* mayInterruptIfRunning */);
930                    return;
931                }
932                setException(convertErrorToException(code, message));
933            }
934        }
935
936    }
937
938    private abstract class BaseFutureTask<T> extends FutureTask<T> {
939        final public IAccountManagerResponse mResponse;
940        final Handler mHandler;
941
942        public BaseFutureTask(Handler handler) {
943            super(new Callable<T>() {
944                public T call() throws Exception {
945                    throw new IllegalStateException("this should never be called");
946                }
947            });
948            mHandler = handler;
949            mResponse = new Response();
950        }
951
952        public abstract void doWork() throws RemoteException;
953
954        public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
955
956        protected void postRunnableToHandler(Runnable runnable) {
957            Handler handler = (mHandler == null) ? mMainHandler : mHandler;
958            handler.post(runnable);
959        }
960
961        protected void startTask() {
962            try {
963                doWork();
964            } catch (RemoteException e) {
965                setException(e);
966            }
967        }
968
969        protected class Response extends IAccountManagerResponse.Stub {
970            public void onResult(Bundle bundle) {
971                try {
972                    T result = bundleToResult(bundle);
973                    if (result == null) {
974                        return;
975                    }
976                    set(result);
977                    return;
978                } catch (ClassCastException e) {
979                    // we will set the exception below
980                } catch (AuthenticatorException e) {
981                    // we will set the exception below
982                }
983                onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
984            }
985
986            public void onError(int code, String message) {
987                if (code == ERROR_CODE_CANCELED) {
988                    cancel(true /* mayInterruptIfRunning */);
989                    return;
990                }
991                setException(convertErrorToException(code, message));
992            }
993        }
994    }
995
996    private abstract class Future2Task<T>
997            extends BaseFutureTask<T> implements AccountManagerFuture<T> {
998        final AccountManagerCallback<T> mCallback;
999        public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
1000            super(handler);
1001            mCallback = callback;
1002        }
1003
1004        protected void done() {
1005            if (mCallback != null) {
1006                postRunnableToHandler(new Runnable() {
1007                    public void run() {
1008                        mCallback.run(Future2Task.this);
1009                    }
1010                });
1011            }
1012        }
1013
1014        public Future2Task<T> start() {
1015            startTask();
1016            return this;
1017        }
1018
1019        private T internalGetResult(Long timeout, TimeUnit unit)
1020                throws OperationCanceledException, IOException, AuthenticatorException {
1021            ensureNotOnMainThread();
1022            try {
1023                if (timeout == null) {
1024                    return get();
1025                } else {
1026                    return get(timeout, unit);
1027                }
1028            } catch (InterruptedException e) {
1029                // fall through and cancel
1030            } catch (TimeoutException e) {
1031                // fall through and cancel
1032            } catch (CancellationException e) {
1033                // fall through and cancel
1034            } catch (ExecutionException e) {
1035                final Throwable cause = e.getCause();
1036                if (cause instanceof IOException) {
1037                    throw (IOException) cause;
1038                } else if (cause instanceof UnsupportedOperationException) {
1039                    throw new AuthenticatorException(cause);
1040                } else if (cause instanceof AuthenticatorException) {
1041                    throw (AuthenticatorException) cause;
1042                } else if (cause instanceof RuntimeException) {
1043                    throw (RuntimeException) cause;
1044                } else if (cause instanceof Error) {
1045                    throw (Error) cause;
1046                } else {
1047                    throw new IllegalStateException(cause);
1048                }
1049            } finally {
1050                cancel(true /* interrupt if running */);
1051            }
1052            throw new OperationCanceledException();
1053        }
1054
1055        public T getResult()
1056                throws OperationCanceledException, IOException, AuthenticatorException {
1057            return internalGetResult(null, null);
1058        }
1059
1060        public T getResult(long timeout, TimeUnit unit)
1061                throws OperationCanceledException, IOException, AuthenticatorException {
1062            return internalGetResult(timeout, unit);
1063        }
1064
1065    }
1066
1067    private Exception convertErrorToException(int code, String message) {
1068        if (code == ERROR_CODE_NETWORK_ERROR) {
1069            return new IOException(message);
1070        }
1071
1072        if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
1073            return new UnsupportedOperationException(message);
1074        }
1075
1076        if (code == ERROR_CODE_INVALID_RESPONSE) {
1077            return new AuthenticatorException(message);
1078        }
1079
1080        if (code == ERROR_CODE_BAD_ARGUMENTS) {
1081            return new IllegalArgumentException(message);
1082        }
1083
1084        return new AuthenticatorException(message);
1085    }
1086
1087    private class GetAuthTokenByTypeAndFeaturesTask
1088            extends AmsTask implements AccountManagerCallback<Bundle> {
1089        GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
1090                final String[] features, Activity activityForPrompting,
1091                final Bundle addAccountOptions, final Bundle loginOptions,
1092                AccountManagerCallback<Bundle> callback, Handler handler) {
1093            super(activityForPrompting, handler, callback);
1094            if (accountType == null) throw new IllegalArgumentException("account type is null");
1095            mAccountType = accountType;
1096            mAuthTokenType = authTokenType;
1097            mFeatures = features;
1098            mAddAccountOptions = addAccountOptions;
1099            mLoginOptions = loginOptions;
1100            mMyCallback = this;
1101        }
1102        volatile AccountManagerFuture<Bundle> mFuture = null;
1103        final String mAccountType;
1104        final String mAuthTokenType;
1105        final String[] mFeatures;
1106        final Bundle mAddAccountOptions;
1107        final Bundle mLoginOptions;
1108        final AccountManagerCallback<Bundle> mMyCallback;
1109
1110        public void doWork() throws RemoteException {
1111            getAccountsByTypeAndFeatures(mAccountType, mFeatures,
1112                    new AccountManagerCallback<Account[]>() {
1113                        public void run(AccountManagerFuture<Account[]> future) {
1114                            Account[] accounts;
1115                            try {
1116                                accounts = future.getResult();
1117                            } catch (OperationCanceledException e) {
1118                                setException(e);
1119                                return;
1120                            } catch (IOException e) {
1121                                setException(e);
1122                                return;
1123                            } catch (AuthenticatorException e) {
1124                                setException(e);
1125                                return;
1126                            }
1127
1128                            if (accounts.length == 0) {
1129                                if (mActivity != null) {
1130                                    // no accounts, add one now. pretend that the user directly
1131                                    // made this request
1132                                    mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
1133                                            mAddAccountOptions, mActivity, mMyCallback, mHandler);
1134                                } else {
1135                                    // send result since we can't prompt to add an account
1136                                    Bundle result = new Bundle();
1137                                    result.putString(KEY_ACCOUNT_NAME, null);
1138                                    result.putString(KEY_ACCOUNT_TYPE, null);
1139                                    result.putString(KEY_AUTHTOKEN, null);
1140                                    try {
1141                                        mResponse.onResult(result);
1142                                    } catch (RemoteException e) {
1143                                        // this will never happen
1144                                    }
1145                                    // we are done
1146                                }
1147                            } else if (accounts.length == 1) {
1148                                // have a single account, return an authtoken for it
1149                                if (mActivity == null) {
1150                                    mFuture = getAuthToken(accounts[0], mAuthTokenType,
1151                                            false /* notifyAuthFailure */, mMyCallback, mHandler);
1152                                } else {
1153                                    mFuture = getAuthToken(accounts[0],
1154                                            mAuthTokenType, mLoginOptions,
1155                                            mActivity, mMyCallback, mHandler);
1156                                }
1157                            } else {
1158                                if (mActivity != null) {
1159                                    IAccountManagerResponse chooseResponse =
1160                                            new IAccountManagerResponse.Stub() {
1161                                        public void onResult(Bundle value) throws RemoteException {
1162                                            Account account = new Account(
1163                                                    value.getString(KEY_ACCOUNT_NAME),
1164                                                    value.getString(KEY_ACCOUNT_TYPE));
1165                                            mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
1166                                                    mActivity, mMyCallback, mHandler);
1167                                        }
1168
1169                                        public void onError(int errorCode, String errorMessage)
1170                                                throws RemoteException {
1171                                            mResponse.onError(errorCode, errorMessage);
1172                                        }
1173                                    };
1174                                    // have many accounts, launch the chooser
1175                                    Intent intent = new Intent();
1176                                    intent.setClassName("android",
1177                                            "android.accounts.ChooseAccountActivity");
1178                                    intent.putExtra(KEY_ACCOUNTS, accounts);
1179                                    intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
1180                                            new AccountManagerResponse(chooseResponse));
1181                                    mActivity.startActivity(intent);
1182                                    // the result will arrive via the IAccountManagerResponse
1183                                } else {
1184                                    // send result since we can't prompt to select an account
1185                                    Bundle result = new Bundle();
1186                                    result.putString(KEY_ACCOUNTS, null);
1187                                    try {
1188                                        mResponse.onResult(result);
1189                                    } catch (RemoteException e) {
1190                                        // this will never happen
1191                                    }
1192                                    // we are done
1193                                }
1194                            }
1195                        }}, mHandler);
1196        }
1197
1198        public void run(AccountManagerFuture<Bundle> future) {
1199            try {
1200                set(future.getResult());
1201            } catch (OperationCanceledException e) {
1202                cancel(true /* mayInterruptIfRUnning */);
1203            } catch (IOException e) {
1204                setException(e);
1205            } catch (AuthenticatorException e) {
1206                setException(e);
1207            }
1208        }
1209    }
1210
1211    /**
1212     * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
1213     * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
1214     * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
1215     * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
1216     * feature set, and addAccountOptions. If there is exactly one then
1217     * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
1218     * called with that account. If there are more than one then a chooser activity is launched
1219     * to prompt the user to select one of them and then the authtoken is retrieved for it,
1220     * <p>
1221     * This call returns immediately but runs asynchronously and the result is accessed via the
1222     * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
1223     * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
1224     * method asynchronously then they will generally pass in a callback object that will get
1225     * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
1226     * they will generally pass null for the callback and instead call
1227     * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
1228     * which will then block until the request completes.
1229     * <p>
1230     * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
1231     *
1232     * @param accountType the accountType to query; this must be non-null
1233     * @param authTokenType the type of authtoken to retrieve; this must be non-null
1234     * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
1235     * @param activityForPrompting The activity used to start any account management
1236     * activities that are required to fulfill this request. This may be null.
1237     * @param addAccountOptions authenticator-specific options used if an account needs to be added
1238     * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
1239     * @param callback A callback to invoke when the request completes. If null then
1240     * no callback is invoked.
1241     * @param handler The {@link Handler} to use to invoke the callback. If null then the
1242     * main thread's {@link Handler} is used.
1243     * @return an {@link AccountManagerFuture} that represents the future result of the call.
1244     * The future result is a {@link Bundle} that contains either:
1245     * <ul>
1246     * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
1247     * fulfill the request.
1248     * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
1249     * request completes successfully.
1250     * </ul>
1251     * If the user presses "back" then the request will be canceled.
1252     */
1253    public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
1254            final String accountType, final String authTokenType, final String[] features,
1255            final Activity activityForPrompting, final Bundle addAccountOptions,
1256            final Bundle getAuthTokenOptions,
1257            final AccountManagerCallback<Bundle> callback, final Handler handler) {
1258        if (accountType == null) throw new IllegalArgumentException("account type is null");
1259        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1260        final GetAuthTokenByTypeAndFeaturesTask task =
1261                new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
1262                activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
1263        task.start();
1264        return task;
1265    }
1266
1267    private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
1268            Maps.newHashMap();
1269
1270    /**
1271     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
1272     * so that it can read the updated list of accounts and send them to the listener
1273     * in mAccountsUpdatedListeners.
1274     */
1275    private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
1276        public void onReceive(final Context context, final Intent intent) {
1277            final Account[] accounts = getAccounts();
1278            // send the result to the listeners
1279            synchronized (mAccountsUpdatedListeners) {
1280                for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
1281                        mAccountsUpdatedListeners.entrySet()) {
1282                    postToHandler(entry.getValue(), entry.getKey(), accounts);
1283                }
1284            }
1285        }
1286    };
1287
1288    /**
1289     * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
1290     * The listener is guaranteed to be invoked on the thread of the Handler that is passed
1291     * in or the main thread's Handler if handler is null.
1292     * <p>
1293     * You must remove this listener before the context that was used to retrieve this
1294     * {@link AccountManager} instance goes away. This generally means when the Activity
1295     * or Service you are running is stopped.
1296     * @param listener the listener to add
1297     * @param handler the Handler whose thread will be used to invoke the listener. If null
1298     * the AccountManager context's main thread will be used.
1299     * @param updateImmediately if true then the listener will be invoked as a result of this
1300     * call.
1301     * @throws IllegalArgumentException if listener is null
1302     * @throws IllegalStateException if listener was already added
1303     */
1304    public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
1305            Handler handler, boolean updateImmediately) {
1306        if (listener == null) {
1307            throw new IllegalArgumentException("the listener is null");
1308        }
1309        synchronized (mAccountsUpdatedListeners) {
1310            if (mAccountsUpdatedListeners.containsKey(listener)) {
1311                throw new IllegalStateException("this listener is already added");
1312            }
1313            final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
1314
1315            mAccountsUpdatedListeners.put(listener, handler);
1316
1317            if (wasEmpty) {
1318                // Register a broadcast receiver to monitor account changes
1319                IntentFilter intentFilter = new IntentFilter();
1320                intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
1321                // To recover from disk-full.
1322                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
1323                mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1324            }
1325        }
1326
1327        if (updateImmediately) {
1328            postToHandler(handler, listener, getAccounts());
1329        }
1330    }
1331
1332    /**
1333     * Remove an {@link OnAccountsUpdateListener} that was previously registered with
1334     * {@link #addOnAccountsUpdatedListener}.
1335     * @param listener the listener to remove
1336     * @throws IllegalArgumentException if listener is null
1337     * @throws IllegalStateException if listener was not already added
1338     */
1339    public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
1340        if (listener == null) {
1341            throw new IllegalArgumentException("the listener is null");
1342        }
1343        synchronized (mAccountsUpdatedListeners) {
1344            if (!mAccountsUpdatedListeners.containsKey(listener)) {
1345                throw new IllegalStateException("this listener was not previously added");
1346            }
1347            mAccountsUpdatedListeners.remove(listener);
1348            if (mAccountsUpdatedListeners.isEmpty()) {
1349                mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1350            }
1351        }
1352    }
1353}
1354