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