AccountManager.java revision c37ee22714ddec1104ba3a2189cf77924ac27812
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.annotation.RequiresPermission;
20import android.annotation.Size;
21import android.app.Activity;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.res.Resources;
28import android.database.SQLException;
29import android.os.Build;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Looper;
33import android.os.Parcelable;
34import android.os.Process;
35import android.os.RemoteException;
36import android.os.UserHandle;
37import android.text.TextUtils;
38import android.util.Log;
39
40import com.android.internal.R;
41import com.google.android.collect.Maps;
42
43import java.io.IOException;
44import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.Map;
47import java.util.concurrent.Callable;
48import java.util.concurrent.CancellationException;
49import java.util.concurrent.ExecutionException;
50import java.util.concurrent.FutureTask;
51import java.util.concurrent.TimeUnit;
52import java.util.concurrent.TimeoutException;
53
54import static android.Manifest.permission.GET_ACCOUNTS;
55
56/**
57 * This class provides access to a centralized registry of the user's
58 * online accounts.  The user enters credentials (username and password) once
59 * per account, granting applications access to online resources with
60 * "one-click" approval.
61 *
62 * <p>Different online services have different ways of handling accounts and
63 * authentication, so the account manager uses pluggable <em>authenticator</em>
64 * modules for different <em>account types</em>.  Authenticators (which may be
65 * written by third parties) handle the actual details of validating account
66 * credentials and storing account information.  For example, Google, Facebook,
67 * and Microsoft Exchange each have their own authenticator.
68 *
69 * <p>Many servers support some notion of an <em>authentication token</em>,
70 * which can be used to authenticate a request to the server without sending
71 * the user's actual password.  (Auth tokens are normally created with a
72 * separate request which does include the user's credentials.)  AccountManager
73 * can generate auth tokens for applications, so the application doesn't need to
74 * handle passwords directly.  Auth tokens are normally reusable and cached by
75 * AccountManager, but must be refreshed periodically.  It's the responsibility
76 * of applications to <em>invalidate</em> auth tokens when they stop working so
77 * the AccountManager knows it needs to regenerate them.
78 *
79 * <p>Applications accessing a server normally go through these steps:
80 *
81 * <ul>
82 * <li>Get an instance of AccountManager using {@link #get(Context)}.
83 *
84 * <li>List the available accounts using {@link #getAccountsByType} or
85 * {@link #getAccountsByTypeAndFeatures}.  Normally applications will only
86 * be interested in accounts with one particular <em>type</em>, which
87 * identifies the authenticator.  Account <em>features</em> are used to
88 * identify particular account subtypes and capabilities.  Both the account
89 * type and features are authenticator-specific strings, and must be known by
90 * the application in coordination with its preferred authenticators.
91 *
92 * <li>Select one or more of the available accounts, possibly by asking the
93 * user for their preference.  If no suitable accounts are available,
94 * {@link #addAccount} may be called to prompt the user to create an
95 * account of the appropriate type.
96 *
97 * <li><b>Important:</b> If the application is using a previously remembered
98 * account selection, it must make sure the account is still in the list
99 * of accounts returned by {@link #getAccountsByType}.  Requesting an auth token
100 * for an account no longer on the device results in an undefined failure.
101 *
102 * <li>Request an auth token for the selected account(s) using one of the
103 * {@link #getAuthToken} methods or related helpers.  Refer to the description
104 * of each method for exact usage and error handling details.
105 *
106 * <li>Make the request using the auth token.  The form of the auth token,
107 * the format of the request, and the protocol used are all specific to the
108 * service you are accessing.  The application may use whatever network and
109 * protocol libraries are useful.
110 *
111 * <li><b>Important:</b> If the request fails with an authentication error,
112 * it could be that a cached auth token is stale and no longer honored by
113 * the server.  The application must call {@link #invalidateAuthToken} to remove
114 * the token from the cache, otherwise requests will continue failing!  After
115 * invalidating the auth token, immediately go back to the "Request an auth
116 * token" step above.  If the process fails the second time, then it can be
117 * treated as a "genuine" authentication failure and the user notified or other
118 * appropriate actions taken.
119 * </ul>
120 *
121 * <p>Some AccountManager methods may need to interact with the user to
122 * prompt for credentials, present options, or ask the user to add an account.
123 * The caller may choose whether to allow AccountManager to directly launch the
124 * necessary user interface and wait for the user, or to return an Intent which
125 * the caller may use to launch the interface, or (in some cases) to install a
126 * notification which the user can select at any time to launch the interface.
127 * To have AccountManager launch the interface directly, the caller must supply
128 * the current foreground {@link Activity} context.
129 *
130 * <p>Many AccountManager methods take {@link AccountManagerCallback} and
131 * {@link Handler} as parameters.  These methods return immediately and
132 * run asynchronously. If a callback is provided then
133 * {@link AccountManagerCallback#run} will be invoked on the Handler's
134 * thread when the request completes, successfully or not.
135 * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
136 * on the {@link AccountManagerFuture} returned by the method (and also passed
137 * to the callback).  This method waits for the operation to complete (if
138 * necessary) and either returns the result or throws an exception if an error
139 * occurred during the operation.  To make the request synchronously, call
140 * {@link AccountManagerFuture#getResult()} immediately on receiving the
141 * future from the method; no callback need be supplied.
142 *
143 * <p>Requests which may block, including
144 * {@link AccountManagerFuture#getResult()}, must never be called on
145 * the application's main event thread.  These operations throw
146 * {@link IllegalStateException} if they are used on the main thread.
147 */
148public class AccountManager {
149    private static final String TAG = "AccountManager";
150
151    public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
152    public static final int ERROR_CODE_NETWORK_ERROR = 3;
153    public static final int ERROR_CODE_CANCELED = 4;
154    public static final int ERROR_CODE_INVALID_RESPONSE = 5;
155    public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
156    public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
157    public static final int ERROR_CODE_BAD_REQUEST = 8;
158    public static final int ERROR_CODE_BAD_AUTHENTICATION = 9;
159
160    /** @hide */
161    public static final int ERROR_CODE_USER_RESTRICTED = 100;
162    /** @hide */
163    public static final int ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE = 101;
164
165    /**
166     * Bundle key used for the {@link String} account name in results
167     * from methods which return information about a particular account.
168     */
169    public static final String KEY_ACCOUNT_NAME = "authAccount";
170
171    /**
172     * Bundle key used for the {@link String} account type in results
173     * from methods which return information about a particular account.
174     */
175    public static final String KEY_ACCOUNT_TYPE = "accountType";
176
177    /**
178     * Bundle key used for the auth token value in results
179     * from {@link #getAuthToken} and friends.
180     */
181    public static final String KEY_AUTHTOKEN = "authtoken";
182
183    /**
184     * Bundle key used for an {@link Intent} in results from methods that
185     * may require the caller to interact with the user.  The Intent can
186     * be used to start the corresponding user interface activity.
187     */
188    public static final String KEY_INTENT = "intent";
189
190    /**
191     * Bundle key used to supply the password directly in options to
192     * {@link #confirmCredentials}, rather than prompting the user with
193     * the standard password prompt.
194     */
195    public static final String KEY_PASSWORD = "password";
196
197    public static final String KEY_ACCOUNTS = "accounts";
198
199    public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
200    public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
201    public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
202    public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
203    public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
204    public static final String KEY_BOOLEAN_RESULT = "booleanResult";
205    public static final String KEY_ERROR_CODE = "errorCode";
206    public static final String KEY_ERROR_MESSAGE = "errorMessage";
207    public static final String KEY_USERDATA = "userdata";
208
209    /**
210     * Bundle key used to supply the last time the credentials of the account
211     * were authenticated successfully. Time is specified in milliseconds since
212     * epoch. Associated time is updated on successful authentication of account
213     * on adding account, confirming credentials, or updating credentials.
214     */
215    public static final String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
216
217    /**
218     * Authenticators using 'customTokens' option will also get the UID of the
219     * caller
220     */
221    public static final String KEY_CALLER_UID = "callerUid";
222    public static final String KEY_CALLER_PID = "callerPid";
223
224    /**
225     * The Android package of the caller will be set in the options bundle by the
226     * {@link AccountManager} and will be passed to the AccountManagerService and
227     * to the AccountAuthenticators. The uid of the caller will be known by the
228     * AccountManagerService as well as the AccountAuthenticators so they will be able to
229     * verify that the package is consistent with the uid (a uid might be shared by many
230     * packages).
231     */
232    public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
233
234    /**
235     * Boolean, if set and 'customTokens' the authenticator is responsible for
236     * notifications.
237     * @hide
238     */
239    public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
240
241    public static final String ACTION_AUTHENTICATOR_INTENT =
242            "android.accounts.AccountAuthenticator";
243    public static final String AUTHENTICATOR_META_DATA_NAME =
244            "android.accounts.AccountAuthenticator";
245    public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
246
247    private final Context mContext;
248    private final IAccountManager mService;
249    private final Handler mMainHandler;
250
251    /**
252     * Action sent as a broadcast Intent by the AccountsService
253     * when accounts are added, accounts are removed, or an
254     * account's credentials (saved password, etc) are changed.
255     *
256     * @see #addOnAccountsUpdatedListener
257     */
258    public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
259        "android.accounts.LOGIN_ACCOUNTS_CHANGED";
260
261    /**
262     * @hide
263     */
264    public AccountManager(Context context, IAccountManager service) {
265        mContext = context;
266        mService = service;
267        mMainHandler = new Handler(mContext.getMainLooper());
268    }
269
270    /**
271     * @hide used for testing only
272     */
273    public AccountManager(Context context, IAccountManager service, Handler handler) {
274        mContext = context;
275        mService = service;
276        mMainHandler = handler;
277    }
278
279    /**
280     * @hide for internal use only
281     */
282    public static Bundle sanitizeResult(Bundle result) {
283        if (result != null) {
284            if (result.containsKey(KEY_AUTHTOKEN)
285                    && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
286                final Bundle newResult = new Bundle(result);
287                newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
288                return newResult;
289            }
290        }
291        return result;
292    }
293
294    /**
295     * Gets an AccountManager instance associated with a Context.
296     * The {@link Context} will be used as long as the AccountManager is
297     * active, so make sure to use a {@link Context} whose lifetime is
298     * commensurate with any listeners registered to
299     * {@link #addOnAccountsUpdatedListener} or similar methods.
300     *
301     * <p>It is safe to call this method from the main thread.
302     *
303     * <p>No permission is required to call this method.
304     *
305     * @param context The {@link Context} to use when necessary
306     * @return An {@link AccountManager} instance
307     */
308    public static AccountManager get(Context context) {
309        if (context == null) throw new IllegalArgumentException("context is null");
310        return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
311    }
312
313    /**
314     * Gets the saved password associated with the account.
315     * This is intended for authenticators and related code; applications
316     * should get an auth token instead.
317     *
318     * <p>It is safe to call this method from the main thread.
319     *
320     * <p>This method requires the caller to have a signature match with the
321     * authenticator that owns the specified account.
322     *
323     * @param account The account to query for a password. Must not be {@code null}.
324     * @return The account's password, null if none or if the account doesn't exist
325     */
326    public String getPassword(final Account account) {
327        if (account == null) throw new IllegalArgumentException("account is null");
328        try {
329            return mService.getPassword(account);
330        } catch (RemoteException e) {
331            // will never happen
332            throw new RuntimeException(e);
333        }
334    }
335
336    /**
337     * Gets the user data named by "key" associated with the account.
338     * This is intended for authenticators and related code to store
339     * arbitrary metadata along with accounts.  The meaning of the keys
340     * and values is up to the authenticator for the account.
341     *
342     * <p>It is safe to call this method from the main thread.
343     *
344     * <p>This method requires the caller to have a signature match with the
345     * authenticator that owns the specified account.
346     *
347     * @param account The account to query for user data
348     * @return The user data, null if the account or key doesn't exist
349     */
350    public String getUserData(final Account account, final String key) {
351        if (account == null) throw new IllegalArgumentException("account is null");
352        if (key == null) throw new IllegalArgumentException("key is null");
353        try {
354            return mService.getUserData(account, key);
355        } catch (RemoteException e) {
356            // will never happen
357            throw new RuntimeException(e);
358        }
359    }
360
361    /**
362     * Lists the currently registered authenticators.
363     *
364     * <p>It is safe to call this method from the main thread.
365     *
366     * <p>No permission is required to call this method.
367     *
368     * @return An array of {@link AuthenticatorDescription} for every
369     *     authenticator known to the AccountManager service.  Empty (never
370     *     null) if no authenticators are known.
371     */
372    public AuthenticatorDescription[] getAuthenticatorTypes() {
373        try {
374            return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
375        } catch (RemoteException e) {
376            // will never happen
377            throw new RuntimeException(e);
378        }
379    }
380
381    /**
382     * @hide
383     * Lists the currently registered authenticators for a given user id.
384     *
385     * <p>It is safe to call this method from the main thread.
386     *
387     * <p>The caller has to be in the same user or have the permission
388     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
389     *
390     * @return An array of {@link AuthenticatorDescription} for every
391     *     authenticator known to the AccountManager service.  Empty (never
392     *     null) if no authenticators are known.
393     */
394    public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
395        try {
396            return mService.getAuthenticatorTypes(userId);
397        } catch (RemoteException e) {
398            // will never happen
399            throw new RuntimeException(e);
400        }
401    }
402
403    /**
404     * Lists all accounts of any type registered on the device.
405     * Equivalent to getAccountsByType(null).
406     *
407     * <p>It is safe to call this method from the main thread.
408     *
409     * <p>This method requires the caller to hold the permission
410     * {@link android.Manifest.permission#GET_ACCOUNTS}.
411     *
412     * @return An array of {@link Account}, one for each account.  Empty
413     *     (never null) if no accounts have been added.
414     */
415    @RequiresPermission(GET_ACCOUNTS)
416    public Account[] getAccounts() {
417        try {
418            return mService.getAccounts(null);
419        } catch (RemoteException e) {
420            // won't ever happen
421            throw new RuntimeException(e);
422        }
423    }
424
425    /**
426     * @hide
427     * Lists all accounts of any type registered on the device for a given
428     * user id. Equivalent to getAccountsByType(null).
429     *
430     * <p>It is safe to call this method from the main thread.
431     *
432     * <p>This method requires the caller to hold the permission
433     * {@link android.Manifest.permission#GET_ACCOUNTS}.
434     *
435     * @return An array of {@link Account}, one for each account.  Empty
436     *     (never null) if no accounts have been added.
437     */
438    @RequiresPermission(GET_ACCOUNTS)
439    public Account[] getAccountsAsUser(int userId) {
440        try {
441            return mService.getAccountsAsUser(null, userId);
442        } catch (RemoteException e) {
443            // won't ever happen
444            throw new RuntimeException(e);
445        }
446    }
447
448    /**
449     * @hide
450     * For use by internal activities. Returns the list of accounts that the calling package
451     * is authorized to use, particularly for shared accounts.
452     * @param packageName package name of the calling app.
453     * @param uid the uid of the calling app.
454     * @return the accounts that are available to this package and user.
455     */
456    public Account[] getAccountsForPackage(String packageName, int uid) {
457        try {
458            return mService.getAccountsForPackage(packageName, uid);
459        } catch (RemoteException re) {
460            // possible security exception
461            throw new RuntimeException(re);
462        }
463    }
464
465    /**
466     * Returns the accounts visible to the specified package, in an environment where some apps
467     * are not authorized to view all accounts. This method can only be called by system apps.
468     * @param type The type of accounts to return, null to retrieve all accounts
469     * @param packageName The package name of the app for which the accounts are to be returned
470     * @return An array of {@link Account}, one per matching account.  Empty
471     *     (never null) if no accounts of the specified type have been added.
472     */
473    public Account[] getAccountsByTypeForPackage(String type, String packageName) {
474        try {
475            return mService.getAccountsByTypeForPackage(type, packageName);
476        } catch (RemoteException re) {
477            // possible security exception
478            throw new RuntimeException(re);
479        }
480    }
481
482    /**
483     * Lists all accounts of a particular type.  The account type is a
484     * string token corresponding to the authenticator and useful domain
485     * of the account.  For example, there are types corresponding to Google
486     * and Facebook.  The exact string token to use will be published somewhere
487     * associated with the authenticator in question.
488     *
489     * <p>It is safe to call this method from the main thread.
490     *
491     * <p>This method requires the caller to hold the permission
492     * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
493     * authenticator that owns the account type.
494     *
495     * @param type The type of accounts to return, null to retrieve all accounts
496     * @return An array of {@link Account}, one per matching account.  Empty
497     *     (never null) if no accounts of the specified type have been added.
498     */
499    @RequiresPermission(GET_ACCOUNTS)
500    public Account[] getAccountsByType(String type) {
501        return getAccountsByTypeAsUser(type, Process.myUserHandle());
502    }
503
504    /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
505    public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
506        try {
507            return mService.getAccountsAsUser(type, userHandle.getIdentifier());
508        } catch (RemoteException e) {
509            // won't ever happen
510            throw new RuntimeException(e);
511        }
512    }
513
514    /**
515     * Change whether or not an app (identified by its uid) is allowed to retrieve an authToken
516     * for an account.
517     * <p>
518     * This is only meant to be used by system activities and is not in the SDK.
519     * @param account The account whose permissions are being modified
520     * @param authTokenType The type of token whose permissions are being modified
521     * @param uid The uid that identifies the app which is being granted or revoked permission.
522     * @param value true is permission is being granted, false for revoked
523     * @hide
524     */
525    public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) {
526        try {
527            mService.updateAppPermission(account, authTokenType, uid, value);
528        } catch (RemoteException e) {
529            // won't ever happen
530            throw new RuntimeException(e);
531        }
532    }
533
534    /**
535     * Get the user-friendly label associated with an authenticator's auth token.
536     * @param accountType the type of the authenticator. must not be null.
537     * @param authTokenType the token type. must not be null.
538     * @param callback callback to invoke when the result is available. may be null.
539     * @param handler the handler on which to invoke the callback, or null for the main thread
540     * @return a future containing the label string
541     * @hide
542     */
543    public AccountManagerFuture<String> getAuthTokenLabel(
544            final String accountType, final String authTokenType,
545            AccountManagerCallback<String> callback, Handler handler) {
546        if (accountType == null) throw new IllegalArgumentException("accountType is null");
547        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
548        return new Future2Task<String>(handler, callback) {
549            public void doWork() throws RemoteException {
550                mService.getAuthTokenLabel(mResponse, accountType, authTokenType);
551            }
552
553            @Override
554            public String bundleToResult(Bundle bundle) throws AuthenticatorException {
555                if (!bundle.containsKey(KEY_AUTH_TOKEN_LABEL)) {
556                    throw new AuthenticatorException("no result in response");
557                }
558                return bundle.getString(KEY_AUTH_TOKEN_LABEL);
559            }
560        }.start();
561    }
562
563    /**
564     * Finds out whether a particular account has all the specified features.
565     * Account features are authenticator-specific string tokens identifying
566     * boolean account properties.  For example, features are used to tell
567     * whether Google accounts have a particular service (such as Google
568     * Calendar or Google Talk) enabled.  The feature names and their meanings
569     * are published somewhere associated with the authenticator in question.
570     *
571     * <p>This method may be called from any thread, but the returned
572     * {@link AccountManagerFuture} must not be used on the main thread.
573     *
574     * <p>This method requires the caller to hold the permission
575     * {@link android.Manifest.permission#GET_ACCOUNTS}.
576     *
577     * @param account The {@link Account} to test
578     * @param features An array of the account features to check
579     * @param callback Callback to invoke when the request completes,
580     *     null for no callback
581     * @param handler {@link Handler} identifying the callback thread,
582     *     null for the main thread
583     * @return An {@link AccountManagerFuture} which resolves to a Boolean,
584     * true if the account exists and has all of the specified features.
585     */
586    @RequiresPermission(GET_ACCOUNTS)
587    public AccountManagerFuture<Boolean> hasFeatures(final Account account,
588            final String[] features,
589            AccountManagerCallback<Boolean> callback, Handler handler) {
590        if (account == null) throw new IllegalArgumentException("account is null");
591        if (features == null) throw new IllegalArgumentException("features is null");
592        return new Future2Task<Boolean>(handler, callback) {
593            public void doWork() throws RemoteException {
594                mService.hasFeatures(mResponse, account, features);
595            }
596            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
597                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
598                    throw new AuthenticatorException("no result in response");
599                }
600                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
601            }
602        }.start();
603    }
604
605    /**
606     * Lists all accounts of a type which have certain features.  The account
607     * type identifies the authenticator (see {@link #getAccountsByType}).
608     * Account features are authenticator-specific string tokens identifying
609     * boolean account properties (see {@link #hasFeatures}).
610     *
611     * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator,
612     * which may contact the server or do other work to check account features,
613     * so the method returns an {@link AccountManagerFuture}.
614     *
615     * <p>This method may be called from any thread, but the returned
616     * {@link AccountManagerFuture} must not be used on the main thread.
617     *
618     * <p>This method requires the caller to hold the permission
619     * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
620     * authenticator that owns the account type.
621     *
622     * @param type The type of accounts to return, must not be null
623     * @param features An array of the account features to require,
624     *     may be null or empty
625     * @param callback Callback to invoke when the request completes,
626     *     null for no callback
627     * @param handler {@link Handler} identifying the callback thread,
628     *     null for the main thread
629     * @return An {@link AccountManagerFuture} which resolves to an array of
630     *     {@link Account}, one per account of the specified type which
631     *     matches the requested features.
632     */
633    @RequiresPermission(GET_ACCOUNTS)
634    public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
635            final String type, final String[] features,
636            AccountManagerCallback<Account[]> callback, Handler handler) {
637        if (type == null) throw new IllegalArgumentException("type is null");
638        return new Future2Task<Account[]>(handler, callback) {
639            public void doWork() throws RemoteException {
640                mService.getAccountsByFeatures(mResponse, type, features);
641            }
642            public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
643                if (!bundle.containsKey(KEY_ACCOUNTS)) {
644                    throw new AuthenticatorException("no result in response");
645                }
646                final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
647                Account[] descs = new Account[parcelables.length];
648                for (int i = 0; i < parcelables.length; i++) {
649                    descs[i] = (Account) parcelables[i];
650                }
651                return descs;
652            }
653        }.start();
654    }
655
656    /**
657     * Adds an account directly to the AccountManager. Normally used by sign-up
658     * wizards associated with authenticators, not directly by applications.
659     * <p>Calling this method does not update the last authenticated timestamp,
660     * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
661     * {@link #notifyAccountAuthenticated(Account)} after getting success.
662     * However, if this method is called when it is triggered by addAccount() or
663     * addAccountAsUser() or similar functions, then there is no need to update
664     * timestamp manually as it is updated automatically by framework on
665     * successful completion of the mentioned functions.
666     * <p>It is safe to call this method from the main thread.
667     * <p>This method requires the caller to have a signature match with the
668     * authenticator that owns the specified account.
669     *
670     * @param account The {@link Account} to add
671     * @param password The password to associate with the account, null for none
672     * @param userdata String values to use for the account's userdata, null for
673     *            none
674     * @return True if the account was successfully added, false if the account
675     *         already exists, the account is null, or another error occurs.
676     */
677    public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
678        if (account == null) throw new IllegalArgumentException("account is null");
679        try {
680            return mService.addAccountExplicitly(account, password, userdata);
681        } catch (RemoteException e) {
682            // won't ever happen
683            throw new RuntimeException(e);
684        }
685    }
686
687    /**
688     * Notifies the system that the account has just been authenticated. This
689     * information may be used by other applications to verify the account. This
690     * should be called only when the user has entered correct credentials for
691     * the account.
692     * <p>
693     * It is not safe to call this method from the main thread. As such, call it
694     * from another thread.
695     * <p>This method requires the caller to have a signature match with the
696     * authenticator that owns the specified account.
697     *
698     * @param account The {@link Account} to be updated.
699     * @return boolean {@code true} if the authentication of the account has been successfully
700     *         acknowledged. Otherwise {@code false}.
701     */
702    public boolean notifyAccountAuthenticated(Account account) {
703        if (account == null)
704            throw new IllegalArgumentException("account is null");
705        try {
706            return mService.accountAuthenticated(account);
707        } catch (RemoteException e) {
708            throw new RuntimeException(e);
709        }
710    }
711
712    /**
713     * Rename the specified {@link Account}.  This is equivalent to removing
714     * the existing account and adding a new renamed account with the old
715     * account's user data.
716     *
717     * <p>It is safe to call this method from the main thread.
718     *
719     * <p>This method requires the caller to have a signature match with the
720     * authenticator that manages the specified account.
721     *
722     * @param account The {@link Account} to rename
723     * @param newName String name to be associated with the account.
724     * @param callback Callback to invoke when the request completes, null for
725     *     no callback
726     * @param handler {@link Handler} identifying the callback thread, null for
727     *     the main thread
728     * @return An {@link AccountManagerFuture} which resolves to the Account
729     *     after the name change. If successful the account's name will be the
730     *     specified new name.
731     */
732    public AccountManagerFuture<Account> renameAccount(
733            final Account account,
734            @Size(min = 1) final String newName,
735            AccountManagerCallback<Account> callback,
736            Handler handler) {
737        if (account == null) throw new IllegalArgumentException("account is null.");
738        if (TextUtils.isEmpty(newName)) {
739              throw new IllegalArgumentException("newName is empty or null.");
740        }
741        return new Future2Task<Account>(handler, callback) {
742            @Override
743            public void doWork() throws RemoteException {
744                mService.renameAccount(mResponse, account, newName);
745            }
746            @Override
747            public Account bundleToResult(Bundle bundle) throws AuthenticatorException {
748                String name = bundle.getString(KEY_ACCOUNT_NAME);
749                String type = bundle.getString(KEY_ACCOUNT_TYPE);
750                return new Account(name, type);
751            }
752        }.start();
753    }
754
755    /**
756     * Gets the previous name associated with the account or {@code null}, if
757     * none. This is intended so that clients of {@link
758     * #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
759     * authenticator has renamed an account.
760     *
761     * <p>It is safe to call this method from the main thread.
762     *
763     * @param account The account to query for a previous name.
764     * @return The account's previous name, null if the account has never been
765     *         renamed.
766     */
767    public String getPreviousName(final Account account) {
768        if (account == null) throw new IllegalArgumentException("account is null");
769        try {
770            return mService.getPreviousName(account);
771        } catch (RemoteException e) {
772            // will never happen
773            throw new RuntimeException(e);
774        }
775    }
776
777    /**
778     * Removes an account from the AccountManager.  Does nothing if the account
779     * does not exist.  Does not delete the account from the server.
780     * The authenticator may have its own policies preventing account
781     * deletion, in which case the account will not be deleted.
782     *
783     * <p>This method requires the caller to have a signature match with the
784     * authenticator that manages the specified account.
785     *
786     * @param account The {@link Account} to remove
787     * @param callback Callback to invoke when the request completes,
788     *     null for no callback
789     * @param handler {@link Handler} identifying the callback thread,
790     *     null for the main thread
791     * @return An {@link AccountManagerFuture} which resolves to a Boolean,
792     *     true if the account has been successfully removed
793     * @deprecated use
794     *     {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
795     *     instead
796     */
797    @Deprecated
798    public AccountManagerFuture<Boolean> removeAccount(final Account account,
799            AccountManagerCallback<Boolean> callback, Handler handler) {
800        if (account == null) throw new IllegalArgumentException("account is null");
801        return new Future2Task<Boolean>(handler, callback) {
802            @Override
803            public void doWork() throws RemoteException {
804                mService.removeAccount(mResponse, account, false);
805            }
806            @Override
807            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
808                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
809                    throw new AuthenticatorException("no result in response");
810                }
811                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
812            }
813        }.start();
814    }
815
816    /**
817     * Removes an account from the AccountManager. Does nothing if the account
818     * does not exist.  Does not delete the account from the server.
819     * The authenticator may have its own policies preventing account
820     * deletion, in which case the account will not be deleted.
821     *
822     * <p>This method may be called from any thread, but the returned
823     * {@link AccountManagerFuture} must not be used on the main thread.
824     *
825     * <p>This method requires the caller to have a signature match with the
826     * authenticator that manages the specified account.
827     *
828     * @param account The {@link Account} to remove
829     * @param activity The {@link Activity} context to use for launching a new
830     *     authenticator-defined sub-Activity to prompt the user to delete an
831     *     account; used only to call startActivity(); if null, the prompt
832     *     will not be launched directly, but the {@link Intent} may be
833     *     returned to the caller instead
834     * @param callback Callback to invoke when the request completes,
835     *     null for no callback
836     * @param handler {@link Handler} identifying the callback thread,
837     *     null for the main thread
838     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
839     *     {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account
840     *     was removed or if active. If no activity was specified, the returned
841     *     Bundle contains only {@link #KEY_INTENT} with the {@link Intent}
842     *     needed to launch the actual account removal process, if authenticator
843     *     needs the activity launch. If an error occurred,
844     *     {@link AccountManagerFuture#getResult()} throws:
845     * <ul>
846     * <li> {@link AuthenticatorException} if no authenticator was registered for
847     *      this account type or the authenticator failed to respond
848     * <li> {@link OperationCanceledException} if the operation was canceled for
849     *      any reason, including the user canceling the creation process or
850     *      adding accounts (of this type) has been disabled by policy
851     * </ul>
852     */
853    public AccountManagerFuture<Bundle> removeAccount(final Account account,
854            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
855        if (account == null) throw new IllegalArgumentException("account is null");
856        return new AmsTask(activity, handler, callback) {
857            @Override
858            public void doWork() throws RemoteException {
859                mService.removeAccount(mResponse, account, activity != null);
860            }
861        }.start();
862    }
863
864    /**
865     * @see #removeAccount(Account, AccountManagerCallback, Handler)
866     * @hide
867     * @deprecated use
868     *     {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)}
869     *     instead
870     */
871    @Deprecated
872    public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
873            AccountManagerCallback<Boolean> callback, Handler handler,
874            final UserHandle userHandle) {
875        if (account == null) throw new IllegalArgumentException("account is null");
876        if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
877        return new Future2Task<Boolean>(handler, callback) {
878            @Override
879            public void doWork() throws RemoteException {
880                mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier());
881            }
882            @Override
883            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
884                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
885                    throw new AuthenticatorException("no result in response");
886                }
887                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
888            }
889        }.start();
890    }
891
892    /**
893     * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler)
894     * @hide
895     */
896    public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account,
897            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler,
898            final UserHandle userHandle) {
899        if (account == null)
900            throw new IllegalArgumentException("account is null");
901        if (userHandle == null)
902            throw new IllegalArgumentException("userHandle is null");
903        return new AmsTask(activity, handler, callback) {
904            public void doWork() throws RemoteException {
905                mService.removeAccountAsUser(mResponse, account, activity != null,
906                        userHandle.getIdentifier());
907            }
908        }.start();
909    }
910
911    /**
912     * Removes an account directly. Normally used by authenticators, not
913     * directly by applications. Does not delete the account from the server.
914     * The authenticator may have its own policies preventing account deletion,
915     * in which case the account will not be deleted.
916     * <p>
917     * It is safe to call this method from the main thread.
918     * <p>This method requires the caller to have a signature match with the
919     * authenticator that manages the specified account.
920     *
921     * @param account The {@link Account} to delete.
922     * @return True if the account was successfully deleted, false if the
923     *         account did not exist, the account is null, or another error
924     *         occurs.
925     */
926    public boolean removeAccountExplicitly(Account account) {
927        if (account == null) throw new IllegalArgumentException("account is null");
928        try {
929            return mService.removeAccountExplicitly(account);
930        } catch (RemoteException e) {
931            // won't ever happen
932            throw new RuntimeException(e);
933        }
934    }
935
936    /**
937     * Removes an auth token from the AccountManager's cache.  Does nothing if
938     * the auth token is not currently in the cache.  Applications must call this
939     * method when the auth token is found to have expired or otherwise become
940     * invalid for authenticating requests.  The AccountManager does not validate
941     * or expire cached auth tokens otherwise.
942     *
943     * <p>It is safe to call this method from the main thread.
944     *
945     * @param accountType The account type of the auth token to invalidate, must not be null
946     * @param authToken The auth token to invalidate, may be null
947     */
948    public void invalidateAuthToken(final String accountType, final String authToken) {
949        if (accountType == null) throw new IllegalArgumentException("accountType is null");
950        try {
951            if (authToken != null) {
952                mService.invalidateAuthToken(accountType, authToken);
953            }
954        } catch (RemoteException e) {
955            // won't ever happen
956            throw new RuntimeException(e);
957        }
958    }
959
960    /**
961     * Gets an auth token from the AccountManager's cache.  If no auth
962     * token is cached for this account, null will be returned -- a new
963     * auth token will not be generated, and the server will not be contacted.
964     * Intended for use by the authenticator, not directly by applications.
965     *
966     * <p>It is safe to call this method from the main thread.
967     *
968     * <p>This method requires the caller to have a signature match with the
969     * authenticator that manages the specified account.
970     *
971     * @param account The account for which an auth token is to be fetched. Cannot be {@code null}.
972     * @param authTokenType The type of auth token to fetch. Cannot be {@code null}.
973     * @return The cached auth token for this account and type, or null if
974     *     no auth token is cached or the account does not exist.
975     * @see #getAuthToken
976     */
977    public String peekAuthToken(final Account account, final String authTokenType) {
978        if (account == null) throw new IllegalArgumentException("account is null");
979        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
980        try {
981            return mService.peekAuthToken(account, authTokenType);
982        } catch (RemoteException e) {
983            // won't ever happen
984            throw new RuntimeException(e);
985        }
986    }
987
988    /**
989     * Sets or forgets a saved password. This modifies the local copy of the
990     * password used to automatically authenticate the user; it does not change
991     * the user's account password on the server. Intended for use by the
992     * authenticator, not directly by applications.
993     * <p>Calling this method does not update the last authenticated timestamp,
994     * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
995     * {@link #notifyAccountAuthenticated(Account)} after getting success.
996     * <p>It is safe to call this method from the main thread.
997     * <p>This method requires the caller to have a signature match with the
998     * authenticator that manages the specified account.
999     *
1000     * @param account The account whose password is to be set. Cannot be
1001     *            {@code null}.
1002     * @param password The password to set, null to clear the password
1003     */
1004    public void setPassword(final Account account, final String password) {
1005        if (account == null) throw new IllegalArgumentException("account is null");
1006        try {
1007            mService.setPassword(account, password);
1008        } catch (RemoteException e) {
1009            // won't ever happen
1010            throw new RuntimeException(e);
1011        }
1012    }
1013
1014    /**
1015     * Forgets a saved password.  This erases the local copy of the password;
1016     * it does not change the user's account password on the server.
1017     * Has the same effect as setPassword(account, null) but requires fewer
1018     * permissions, and may be used by applications or management interfaces
1019     * to "sign out" from an account.
1020     *
1021     * <p>This method only successfully clear the account's password when the
1022     * caller has the same signature as the authenticator that owns the
1023     * specified account. Otherwise, this method will silently fail.
1024     *
1025     * <p>It is safe to call this method from the main thread.
1026     *
1027     * @param account The account whose password to clear
1028     */
1029    public void clearPassword(final Account account) {
1030        if (account == null) throw new IllegalArgumentException("account is null");
1031        try {
1032            mService.clearPassword(account);
1033        } catch (RemoteException e) {
1034            // won't ever happen
1035            throw new RuntimeException(e);
1036        }
1037    }
1038
1039    /**
1040     * Sets one userdata key for an account.  Intended by use for the
1041     * authenticator to stash state for itself, not directly by applications.
1042     * The meaning of the keys and values is up to the authenticator.
1043     *
1044     * <p>It is safe to call this method from the main thread.
1045     *
1046     * <p>This method requires the caller to have a signature match with the
1047     * authenticator that manages the specified account.
1048     *
1049     * @param account Account whose user data is to be set. Must not be {@code null}.
1050     * @param key String user data key to set.  Must not be null
1051     * @param value String value to set, {@code null} to clear this user data key
1052     */
1053    public void setUserData(final Account account, final String key, final String value) {
1054        if (account == null) throw new IllegalArgumentException("account is null");
1055        if (key == null) throw new IllegalArgumentException("key is null");
1056        try {
1057            mService.setUserData(account, key, value);
1058        } catch (RemoteException e) {
1059            // won't ever happen
1060            throw new RuntimeException(e);
1061        }
1062    }
1063
1064    /**
1065     * Adds an auth token to the AccountManager cache for an account.
1066     * If the account does not exist then this call has no effect.
1067     * Replaces any previous auth token for this account and auth token type.
1068     * Intended for use by the authenticator, not directly by applications.
1069     *
1070     * <p>It is safe to call this method from the main thread.
1071     *
1072     * <p>This method requires the caller to have a signature match with the
1073     * authenticator that manages the specified account.
1074     *
1075     * @param account The account to set an auth token for
1076     * @param authTokenType The type of the auth token, see {#getAuthToken}
1077     * @param authToken The auth token to add to the cache
1078     */
1079    public void setAuthToken(Account account, final String authTokenType, final String authToken) {
1080        if (account == null) throw new IllegalArgumentException("account is null");
1081        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1082        try {
1083            mService.setAuthToken(account, authTokenType, authToken);
1084        } catch (RemoteException e) {
1085            // won't ever happen
1086            throw new RuntimeException(e);
1087        }
1088    }
1089
1090    /**
1091     * This convenience helper synchronously gets an auth token with
1092     * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
1093     *
1094     * <p>This method may block while a network request completes, and must
1095     * never be made from the main thread.
1096     *
1097     * @param account The account to fetch an auth token for
1098     * @param authTokenType The auth token type, see {@link #getAuthToken getAuthToken()}
1099     * @param notifyAuthFailure If true, display a notification and return null
1100     *     if authentication fails; if false, prompt and wait for the user to
1101     *     re-enter correct credentials before returning
1102     * @return An auth token of the specified type for this account, or null
1103     *     if authentication fails or none can be fetched.
1104     * @throws AuthenticatorException if the authenticator failed to respond
1105     * @throws OperationCanceledException if the request was canceled for any
1106     *     reason, including the user canceling a credential request
1107     * @throws java.io.IOException if the authenticator experienced an I/O problem
1108     *     creating a new auth token, usually because of network trouble
1109     */
1110    public String blockingGetAuthToken(Account account, String authTokenType,
1111            boolean notifyAuthFailure)
1112            throws OperationCanceledException, IOException, AuthenticatorException {
1113        if (account == null) throw new IllegalArgumentException("account is null");
1114        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1115        Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
1116                null /* handler */).getResult();
1117        if (bundle == null) {
1118            // This should never happen, but it does, occasionally. If it does return null to
1119            // signify that we were not able to get the authtoken.
1120            // TODO: remove this when the bug is found that sometimes causes a null bundle to be
1121            // returned
1122            Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
1123                    + account + ", authTokenType " + authTokenType);
1124            return null;
1125        }
1126        return bundle.getString(KEY_AUTHTOKEN);
1127    }
1128
1129    /**
1130     * Gets an auth token of the specified type for a particular account,
1131     * prompting the user for credentials if necessary.  This method is
1132     * intended for applications running in the foreground where it makes
1133     * sense to ask the user directly for a password.
1134     *
1135     * <p>If a previously generated auth token is cached for this account and
1136     * type, then it is returned.  Otherwise, if a saved password is
1137     * available, it is sent to the server to generate a new auth token.
1138     * Otherwise, the user is prompted to enter a password.
1139     *
1140     * <p>Some authenticators have auth token <em>types</em>, whose value
1141     * is authenticator-dependent.  Some services use different token types to
1142     * access different functionality -- for example, Google uses different auth
1143     * tokens to access Gmail and Google Calendar for the same account.
1144     *
1145     * <p>This method may be called from any thread, but the returned
1146     * {@link AccountManagerFuture} must not be used on the main thread.
1147     *
1148     * @param account The account to fetch an auth token for
1149     * @param authTokenType The auth token type, an authenticator-dependent
1150     *     string token, must not be null
1151     * @param options Authenticator-specific options for the request,
1152     *     may be null or empty
1153     * @param activity The {@link Activity} context to use for launching a new
1154     *     authenticator-defined sub-Activity to prompt the user for a password
1155     *     if necessary; used only to call startActivity(); must not be null.
1156     * @param callback Callback to invoke when the request completes,
1157     *     null for no callback
1158     * @param handler {@link Handler} identifying the callback thread,
1159     *     null for the main thread
1160     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
1161     *     at least the following fields:
1162     * <ul>
1163     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
1164     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1165     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
1166     * </ul>
1167     *
1168     * (Other authenticator-specific values may be returned.)  If an auth token
1169     * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
1170     * <ul>
1171     * <li> {@link AuthenticatorException} if the authenticator failed to respond
1172     * <li> {@link OperationCanceledException} if the operation is canceled for
1173     *      any reason, incluidng the user canceling a credential request
1174     * <li> {@link IOException} if the authenticator experienced an I/O problem
1175     *      creating a new auth token, usually because of network trouble
1176     * </ul>
1177     * If the account is no longer present on the device, the return value is
1178     * authenticator-dependent.  The caller should verify the validity of the
1179     * account before requesting an auth token.
1180     */
1181    public AccountManagerFuture<Bundle> getAuthToken(
1182            final Account account, final String authTokenType, final Bundle options,
1183            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
1184        if (account == null) throw new IllegalArgumentException("account is null");
1185        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1186        final Bundle optionsIn = new Bundle();
1187        if (options != null) {
1188            optionsIn.putAll(options);
1189        }
1190        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
1191        return new AmsTask(activity, handler, callback) {
1192            public void doWork() throws RemoteException {
1193                mService.getAuthToken(mResponse, account, authTokenType,
1194                        false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
1195                        optionsIn);
1196            }
1197        }.start();
1198    }
1199
1200    /**
1201     * Gets an auth token of the specified type for a particular account,
1202     * optionally raising a notification if the user must enter credentials.
1203     * This method is intended for background tasks and services where the
1204     * user should not be immediately interrupted with a password prompt.
1205     *
1206     * <p>If a previously generated auth token is cached for this account and
1207     * type, then it is returned.  Otherwise, if a saved password is
1208     * available, it is sent to the server to generate a new auth token.
1209     * Otherwise, an {@link Intent} is returned which, when started, will
1210     * prompt the user for a password.  If the notifyAuthFailure parameter is
1211     * set, a status bar notification is also created with the same Intent,
1212     * alerting the user that they need to enter a password at some point.
1213     *
1214     * <p>In that case, you may need to wait until the user responds, which
1215     * could take hours or days or forever.  When the user does respond and
1216     * supply a new password, the account manager will broadcast the
1217     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
1218     * use to try again.
1219     *
1220     * <p>If notifyAuthFailure is not set, it is the application's
1221     * responsibility to launch the returned Intent at some point.
1222     * Either way, the result from this call will not wait for user action.
1223     *
1224     * <p>Some authenticators have auth token <em>types</em>, whose value
1225     * is authenticator-dependent.  Some services use different token types to
1226     * access different functionality -- for example, Google uses different auth
1227     * tokens to access Gmail and Google Calendar for the same account.
1228     *
1229     * <p>This method may be called from any thread, but the returned
1230     * {@link AccountManagerFuture} must not be used on the main thread.
1231     *
1232     * @param account The account to fetch an auth token for
1233     * @param authTokenType The auth token type, an authenticator-dependent
1234     *     string token, must not be null
1235     * @param notifyAuthFailure True to add a notification to prompt the
1236     *     user for a password if necessary, false to leave that to the caller
1237     * @param callback Callback to invoke when the request completes,
1238     *     null for no callback
1239     * @param handler {@link Handler} identifying the callback thread,
1240     *     null for the main thread
1241     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
1242     *     at least the following fields on success:
1243     * <ul>
1244     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
1245     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1246     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
1247     * </ul>
1248     *
1249     * (Other authenticator-specific values may be returned.)  If the user
1250     * must enter credentials, the returned Bundle contains only
1251     * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
1252     *
1253     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
1254     * <ul>
1255     * <li> {@link AuthenticatorException} if the authenticator failed to respond
1256     * <li> {@link OperationCanceledException} if the operation is canceled for
1257     *      any reason, incluidng the user canceling a credential request
1258     * <li> {@link IOException} if the authenticator experienced an I/O problem
1259     *      creating a new auth token, usually because of network trouble
1260     * </ul>
1261     * If the account is no longer present on the device, the return value is
1262     * authenticator-dependent.  The caller should verify the validity of the
1263     * account before requesting an auth token.
1264     * @deprecated use {@link #getAuthToken(Account, String, android.os.Bundle,
1265     * boolean, AccountManagerCallback, android.os.Handler)} instead
1266     */
1267    @Deprecated
1268    public AccountManagerFuture<Bundle> getAuthToken(
1269            final Account account, final String authTokenType,
1270            final boolean notifyAuthFailure,
1271            AccountManagerCallback<Bundle> callback, Handler handler) {
1272        return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
1273                handler);
1274    }
1275
1276    /**
1277     * Gets an auth token of the specified type for a particular account,
1278     * optionally raising a notification if the user must enter credentials.
1279     * This method is intended for background tasks and services where the
1280     * user should not be immediately interrupted with a password prompt.
1281     *
1282     * <p>If a previously generated auth token is cached for this account and
1283     * type, then it is returned.  Otherwise, if a saved password is
1284     * available, it is sent to the server to generate a new auth token.
1285     * Otherwise, an {@link Intent} is returned which, when started, will
1286     * prompt the user for a password.  If the notifyAuthFailure parameter is
1287     * set, a status bar notification is also created with the same Intent,
1288     * alerting the user that they need to enter a password at some point.
1289     *
1290     * <p>In that case, you may need to wait until the user responds, which
1291     * could take hours or days or forever.  When the user does respond and
1292     * supply a new password, the account manager will broadcast the
1293     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
1294     * use to try again.
1295     *
1296     * <p>If notifyAuthFailure is not set, it is the application's
1297     * responsibility to launch the returned Intent at some point.
1298     * Either way, the result from this call will not wait for user action.
1299     *
1300     * <p>Some authenticators have auth token <em>types</em>, whose value
1301     * is authenticator-dependent.  Some services use different token types to
1302     * access different functionality -- for example, Google uses different auth
1303     * tokens to access Gmail and Google Calendar for the same account.
1304     *
1305     * <p>This method may be called from any thread, but the returned
1306     * {@link AccountManagerFuture} must not be used on the main thread.
1307     *
1308     * @param account The account to fetch an auth token for
1309     * @param authTokenType The auth token type, an authenticator-dependent
1310     *     string token, must not be null
1311     * @param options Authenticator-specific options for the request,
1312     *     may be null or empty
1313     * @param notifyAuthFailure True to add a notification to prompt the
1314     *     user for a password if necessary, false to leave that to the caller
1315     * @param callback Callback to invoke when the request completes,
1316     *     null for no callback
1317     * @param handler {@link Handler} identifying the callback thread,
1318     *     null for the main thread
1319     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
1320     *     at least the following fields on success:
1321     * <ul>
1322     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
1323     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1324     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
1325     * </ul>
1326     *
1327     * (Other authenticator-specific values may be returned.)  If the user
1328     * must enter credentials, the returned Bundle contains only
1329     * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
1330     *
1331     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
1332     * <ul>
1333     * <li> {@link AuthenticatorException} if the authenticator failed to respond
1334     * <li> {@link OperationCanceledException} if the operation is canceled for
1335     *      any reason, incluidng the user canceling a credential request
1336     * <li> {@link IOException} if the authenticator experienced an I/O problem
1337     *      creating a new auth token, usually because of network trouble
1338     * </ul>
1339     * If the account is no longer present on the device, the return value is
1340     * authenticator-dependent.  The caller should verify the validity of the
1341     * account before requesting an auth token.
1342     */
1343    public AccountManagerFuture<Bundle> getAuthToken(
1344            final Account account, final String authTokenType, final Bundle options,
1345            final boolean notifyAuthFailure,
1346            AccountManagerCallback<Bundle> callback, Handler handler) {
1347
1348        if (account == null) throw new IllegalArgumentException("account is null");
1349        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
1350        final Bundle optionsIn = new Bundle();
1351        if (options != null) {
1352            optionsIn.putAll(options);
1353        }
1354        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
1355        return new AmsTask(null, handler, callback) {
1356            public void doWork() throws RemoteException {
1357                mService.getAuthToken(mResponse, account, authTokenType,
1358                        notifyAuthFailure, false /* expectActivityLaunch */, optionsIn);
1359            }
1360        }.start();
1361    }
1362
1363    /**
1364     * Asks the user to add an account of a specified type.  The authenticator
1365     * for this account type processes this request with the appropriate user
1366     * interface.  If the user does elect to create a new account, the account
1367     * name is returned.
1368     *
1369     * <p>This method may be called from any thread, but the returned
1370     * {@link AccountManagerFuture} must not be used on the main thread.
1371     *
1372     * @param accountType The type of account to add; must not be null
1373     * @param authTokenType The type of auth token (see {@link #getAuthToken})
1374     *     this account will need to be able to generate, null for none
1375     * @param requiredFeatures The features (see {@link #hasFeatures}) this
1376     *     account must have, null for none
1377     * @param addAccountOptions Authenticator-specific options for the request,
1378     *     may be null or empty
1379     * @param activity The {@link Activity} context to use for launching a new
1380     *     authenticator-defined sub-Activity to prompt the user to create an
1381     *     account; used only to call startActivity(); if null, the prompt
1382     *     will not be launched directly, but the necessary {@link Intent}
1383     *     will be returned to the caller instead
1384     * @param callback Callback to invoke when the request completes,
1385     *     null for no callback
1386     * @param handler {@link Handler} identifying the callback thread,
1387     *     null for the main thread
1388     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
1389     *     these fields if activity was specified and an account was created:
1390     * <ul>
1391     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
1392     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1393     * </ul>
1394     *
1395     * If no activity was specified, the returned Bundle contains only
1396     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
1397     * actual account creation process.  If an error occurred,
1398     * {@link AccountManagerFuture#getResult()} throws:
1399     * <ul>
1400     * <li> {@link AuthenticatorException} if no authenticator was registered for
1401     *      this account type or the authenticator failed to respond
1402     * <li> {@link OperationCanceledException} if the operation was canceled for
1403     *      any reason, including the user canceling the creation process or adding accounts
1404     *      (of this type) has been disabled by policy
1405     * <li> {@link IOException} if the authenticator experienced an I/O problem
1406     *      creating a new account, usually because of network trouble
1407     * </ul>
1408     */
1409    public AccountManagerFuture<Bundle> addAccount(final String accountType,
1410            final String authTokenType, final String[] requiredFeatures,
1411            final Bundle addAccountOptions,
1412            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
1413        if (accountType == null) throw new IllegalArgumentException("accountType is null");
1414        final Bundle optionsIn = new Bundle();
1415        if (addAccountOptions != null) {
1416            optionsIn.putAll(addAccountOptions);
1417        }
1418        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
1419
1420        return new AmsTask(activity, handler, callback) {
1421            public void doWork() throws RemoteException {
1422                mService.addAccount(mResponse, accountType, authTokenType,
1423                        requiredFeatures, activity != null, optionsIn);
1424            }
1425        }.start();
1426    }
1427
1428    /**
1429     * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler)
1430     * @hide
1431     */
1432    public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType,
1433            final String authTokenType, final String[] requiredFeatures,
1434            final Bundle addAccountOptions, final Activity activity,
1435            AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) {
1436        if (accountType == null) throw new IllegalArgumentException("accountType is null");
1437        if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
1438        final Bundle optionsIn = new Bundle();
1439        if (addAccountOptions != null) {
1440            optionsIn.putAll(addAccountOptions);
1441        }
1442        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
1443
1444        return new AmsTask(activity, handler, callback) {
1445            public void doWork() throws RemoteException {
1446                mService.addAccountAsUser(mResponse, accountType, authTokenType,
1447                        requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier());
1448            }
1449        }.start();
1450    }
1451
1452    /**
1453     * Adds a shared account from the primary user to a secondary user. Adding the shared account
1454     * doesn't take effect immediately. When the target user starts up, any pending shared accounts
1455     * are attempted to be copied to the target user from the primary via calls to the
1456     * authenticator.
1457     * @param account the account to share
1458     * @param user the target user
1459     * @return
1460     * @hide
1461     */
1462    public boolean addSharedAccount(final Account account, UserHandle user) {
1463        try {
1464            boolean val = mService.addSharedAccountAsUser(account, user.getIdentifier());
1465            return val;
1466        } catch (RemoteException re) {
1467            // won't ever happen
1468            throw new RuntimeException(re);
1469        }
1470    }
1471
1472    /**
1473     * Copies an account from the primary user to another user.
1474     * @param account the account to copy
1475     * @param user the target user
1476     * @param callback Callback to invoke when the request completes,
1477     *     null for no callback
1478     * @param handler {@link Handler} identifying the callback thread,
1479     *     null for the main thread
1480     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
1481     * succeeded.
1482     * @hide
1483     */
1484    public AccountManagerFuture<Boolean> copyAccountToUser(
1485            final Account account, final UserHandle user,
1486            AccountManagerCallback<Boolean> callback, Handler handler) {
1487        if (account == null) throw new IllegalArgumentException("account is null");
1488        if (user == null) throw new IllegalArgumentException("user is null");
1489
1490        return new Future2Task<Boolean>(handler, callback) {
1491            @Override
1492            public void doWork() throws RemoteException {
1493                mService.copyAccountToUser(
1494                        mResponse, account, UserHandle.USER_OWNER, user.getIdentifier());
1495            }
1496            @Override
1497            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
1498                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
1499                    throw new AuthenticatorException("no result in response");
1500                }
1501                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
1502            }
1503        }.start();
1504    }
1505
1506    /**
1507     * @hide
1508     * Removes the shared account.
1509     * @param account the account to remove
1510     * @param user the user to remove the account from
1511     * @return
1512     */
1513    public boolean removeSharedAccount(final Account account, UserHandle user) {
1514        try {
1515            boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
1516            return val;
1517        } catch (RemoteException re) {
1518            // won't ever happen
1519            throw new RuntimeException(re);
1520        }
1521    }
1522
1523    /**
1524     * @hide
1525     * @param user
1526     * @return
1527     */
1528    public Account[] getSharedAccounts(UserHandle user) {
1529        try {
1530            return mService.getSharedAccountsAsUser(user.getIdentifier());
1531        } catch (RemoteException re) {
1532            // won't ever happen
1533            throw new RuntimeException(re);
1534        }
1535    }
1536
1537    /**
1538     * Confirms that the user knows the password for an account to make extra
1539     * sure they are the owner of the account.  The user-entered password can
1540     * be supplied directly, otherwise the authenticator for this account type
1541     * prompts the user with the appropriate interface.  This method is
1542     * intended for applications which want extra assurance; for example, the
1543     * phone lock screen uses this to let the user unlock the phone with an
1544     * account password if they forget the lock pattern.
1545     *
1546     * <p>If the user-entered password matches a saved password for this
1547     * account, the request is considered valid; otherwise the authenticator
1548     * verifies the password (usually by contacting the server).
1549     *
1550     * <p>This method may be called from any thread, but the returned
1551     * {@link AccountManagerFuture} must not be used on the main thread.
1552     *
1553     * @param account The account to confirm password knowledge for
1554     * @param options Authenticator-specific options for the request;
1555     *     if the {@link #KEY_PASSWORD} string field is present, the
1556     *     authenticator may use it directly rather than prompting the user;
1557     *     may be null or empty
1558     * @param activity The {@link Activity} context to use for launching a new
1559     *     authenticator-defined sub-Activity to prompt the user to enter a
1560     *     password; used only to call startActivity(); if null, the prompt
1561     *     will not be launched directly, but the necessary {@link Intent}
1562     *     will be returned to the caller instead
1563     * @param callback Callback to invoke when the request completes,
1564     *     null for no callback
1565     * @param handler {@link Handler} identifying the callback thread,
1566     *     null for the main thread
1567     * @return An {@link AccountManagerFuture} which resolves to a Bundle
1568     *     with these fields if activity or password was supplied and
1569     *     the account was successfully verified:
1570     * <ul>
1571     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
1572     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1573     * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
1574     * </ul>
1575     *
1576     * If no activity or password was specified, the returned Bundle contains
1577     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
1578     * password prompt.
1579     *
1580     * <p>Also the returning Bundle may contain {@link
1581     * #KEY_LAST_AUTHENTICATED_TIME} indicating the last time the
1582     * credential was validated/created.
1583     *
1584     * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
1585     * <ul>
1586     * <li> {@link AuthenticatorException} if the authenticator failed to respond
1587     * <li> {@link OperationCanceledException} if the operation was canceled for
1588     *      any reason, including the user canceling the password prompt
1589     * <li> {@link IOException} if the authenticator experienced an I/O problem
1590     *      verifying the password, usually because of network trouble
1591     * </ul>
1592     */
1593    public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
1594            final Bundle options,
1595            final Activity activity,
1596            final AccountManagerCallback<Bundle> callback,
1597            final Handler handler) {
1598        return confirmCredentialsAsUser(account, options, activity, callback, handler,
1599                Process.myUserHandle());
1600    }
1601
1602    /**
1603     * @hide
1604     * Same as {@link #confirmCredentials(Account, Bundle, Activity, AccountManagerCallback, Handler)}
1605     * but for the specified user.
1606     */
1607    public AccountManagerFuture<Bundle> confirmCredentialsAsUser(final Account account,
1608            final Bundle options,
1609            final Activity activity,
1610            final AccountManagerCallback<Bundle> callback,
1611            final Handler handler, UserHandle userHandle) {
1612        if (account == null) throw new IllegalArgumentException("account is null");
1613        final int userId = userHandle.getIdentifier();
1614        return new AmsTask(activity, handler, callback) {
1615            public void doWork() throws RemoteException {
1616                mService.confirmCredentialsAsUser(mResponse, account, options, activity != null,
1617                        userId);
1618            }
1619        }.start();
1620    }
1621
1622    /**
1623     * Asks the user to enter a new password for an account, updating the
1624     * saved credentials for the account.  Normally this happens automatically
1625     * when the server rejects credentials during an auth token fetch, but this
1626     * can be invoked directly to ensure we have the correct credentials stored.
1627     *
1628     * <p>This method may be called from any thread, but the returned
1629     * {@link AccountManagerFuture} must not be used on the main thread.
1630     *
1631     * @param account The account to update credentials for
1632     * @param authTokenType The credentials entered must allow an auth token
1633     *     of this type to be created (but no actual auth token is returned);
1634     *     may be null
1635     * @param options Authenticator-specific options for the request;
1636     *     may be null or empty
1637     * @param activity The {@link Activity} context to use for launching a new
1638     *     authenticator-defined sub-Activity to prompt the user to enter a
1639     *     password; used only to call startActivity(); if null, the prompt
1640     *     will not be launched directly, but the necessary {@link Intent}
1641     *     will be returned to the caller instead
1642     * @param callback Callback to invoke when the request completes,
1643     *     null for no callback
1644     * @param handler {@link Handler} identifying the callback thread,
1645     *     null for the main thread
1646     * @return An {@link AccountManagerFuture} which resolves to a Bundle
1647     *     with these fields if an activity was supplied and the account
1648     *     credentials were successfully updated:
1649     * <ul>
1650     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
1651     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
1652     * </ul>
1653     *
1654     * If no activity was specified, the returned Bundle contains
1655     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
1656     * password prompt. If an error occurred,
1657     * {@link AccountManagerFuture#getResult()} throws:
1658     * <ul>
1659     * <li> {@link AuthenticatorException} if the authenticator failed to respond
1660     * <li> {@link OperationCanceledException} if the operation was canceled for
1661     *      any reason, including the user canceling the password prompt
1662     * <li> {@link IOException} if the authenticator experienced an I/O problem
1663     *      verifying the password, usually because of network trouble
1664     * </ul>
1665     */
1666    public AccountManagerFuture<Bundle> updateCredentials(final Account account,
1667            final String authTokenType,
1668            final Bundle options, final Activity activity,
1669            final AccountManagerCallback<Bundle> callback,
1670            final Handler handler) {
1671        if (account == null) throw new IllegalArgumentException("account is null");
1672        return new AmsTask(activity, handler, callback) {
1673            public void doWork() throws RemoteException {
1674                mService.updateCredentials(mResponse, account, authTokenType, activity != null,
1675                        options);
1676            }
1677        }.start();
1678    }
1679
1680    /**
1681     * Offers the user an opportunity to change an authenticator's settings.
1682     * These properties are for the authenticator in general, not a particular
1683     * account.  Not all authenticators support this method.
1684     *
1685     * <p>This method may be called from any thread, but the returned
1686     * {@link AccountManagerFuture} must not be used on the main thread.
1687     *
1688     * <p>This method requires the caller to have the same signature as the
1689     * authenticator associated with the specified account type.
1690     *
1691     * @param accountType The account type associated with the authenticator
1692     *     to adjust
1693     * @param activity The {@link Activity} context to use for launching a new
1694     *     authenticator-defined sub-Activity to adjust authenticator settings;
1695     *     used only to call startActivity(); if null, the settings dialog will
1696     *     not be launched directly, but the necessary {@link Intent} will be
1697     *     returned to the caller instead
1698     * @param callback Callback to invoke when the request completes,
1699     *     null for no callback
1700     * @param handler {@link Handler} identifying the callback thread,
1701     *     null for the main thread
1702     * @return An {@link AccountManagerFuture} which resolves to a Bundle
1703     *     which is empty if properties were edited successfully, or
1704     *     if no activity was specified, contains only {@link #KEY_INTENT}
1705     *     needed to launch the authenticator's settings dialog.
1706     *     If an error occurred, {@link AccountManagerFuture#getResult()}
1707     *     throws:
1708     * <ul>
1709     * <li> {@link AuthenticatorException} if no authenticator was registered for
1710     *      this account type or the authenticator failed to respond
1711     * <li> {@link OperationCanceledException} if the operation was canceled for
1712     *      any reason, including the user canceling the settings dialog
1713     * <li> {@link IOException} if the authenticator experienced an I/O problem
1714     *      updating settings, usually because of network trouble
1715     * </ul>
1716     */
1717    public AccountManagerFuture<Bundle> editProperties(final String accountType,
1718            final Activity activity, final AccountManagerCallback<Bundle> callback,
1719            final Handler handler) {
1720        if (accountType == null) throw new IllegalArgumentException("accountType is null");
1721        return new AmsTask(activity, handler, callback) {
1722            public void doWork() throws RemoteException {
1723                mService.editProperties(mResponse, accountType, activity != null);
1724            }
1725        }.start();
1726    }
1727
1728    private void ensureNotOnMainThread() {
1729        final Looper looper = Looper.myLooper();
1730        if (looper != null && looper == mContext.getMainLooper()) {
1731            final IllegalStateException exception = new IllegalStateException(
1732                    "calling this from your main thread can lead to deadlock");
1733            Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
1734                    exception);
1735            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
1736                throw exception;
1737            }
1738        }
1739    }
1740
1741    private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
1742            final AccountManagerFuture<Bundle> future) {
1743        handler = handler == null ? mMainHandler : handler;
1744        handler.post(new Runnable() {
1745            public void run() {
1746                callback.run(future);
1747            }
1748        });
1749    }
1750
1751    private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
1752            final Account[] accounts) {
1753        final Account[] accountsCopy = new Account[accounts.length];
1754        // send a copy to make sure that one doesn't
1755        // change what another sees
1756        System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
1757        handler = (handler == null) ? mMainHandler : handler;
1758        handler.post(new Runnable() {
1759            public void run() {
1760                try {
1761                    listener.onAccountsUpdated(accountsCopy);
1762                } catch (SQLException e) {
1763                    // Better luck next time.  If the problem was disk-full,
1764                    // the STORAGE_OK intent will re-trigger the update.
1765                    Log.e(TAG, "Can't update accounts", e);
1766                }
1767            }
1768        });
1769    }
1770
1771    private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
1772        final IAccountManagerResponse mResponse;
1773        final Handler mHandler;
1774        final AccountManagerCallback<Bundle> mCallback;
1775        final Activity mActivity;
1776        public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
1777            super(new Callable<Bundle>() {
1778                public Bundle call() throws Exception {
1779                    throw new IllegalStateException("this should never be called");
1780                }
1781            });
1782
1783            mHandler = handler;
1784            mCallback = callback;
1785            mActivity = activity;
1786            mResponse = new Response();
1787        }
1788
1789        public final AccountManagerFuture<Bundle> start() {
1790            try {
1791                doWork();
1792            } catch (RemoteException e) {
1793                setException(e);
1794            }
1795            return this;
1796        }
1797
1798        protected void set(Bundle bundle) {
1799            // TODO: somehow a null is being set as the result of the Future. Log this
1800            // case to help debug where this is occurring. When this bug is fixed this
1801            // condition statement should be removed.
1802            if (bundle == null) {
1803                Log.e(TAG, "the bundle must not be null", new Exception());
1804            }
1805            super.set(bundle);
1806        }
1807
1808        public abstract void doWork() throws RemoteException;
1809
1810        private Bundle internalGetResult(Long timeout, TimeUnit unit)
1811                throws OperationCanceledException, IOException, AuthenticatorException {
1812            if (!isDone()) {
1813                ensureNotOnMainThread();
1814            }
1815            try {
1816                if (timeout == null) {
1817                    return get();
1818                } else {
1819                    return get(timeout, unit);
1820                }
1821            } catch (CancellationException e) {
1822                throw new OperationCanceledException();
1823            } catch (TimeoutException e) {
1824                // fall through and cancel
1825            } catch (InterruptedException e) {
1826                // fall through and cancel
1827            } catch (ExecutionException e) {
1828                final Throwable cause = e.getCause();
1829                if (cause instanceof IOException) {
1830                    throw (IOException) cause;
1831                } else if (cause instanceof UnsupportedOperationException) {
1832                    throw new AuthenticatorException(cause);
1833                } else if (cause instanceof AuthenticatorException) {
1834                    throw (AuthenticatorException) cause;
1835                } else if (cause instanceof RuntimeException) {
1836                    throw (RuntimeException) cause;
1837                } else if (cause instanceof Error) {
1838                    throw (Error) cause;
1839                } else {
1840                    throw new IllegalStateException(cause);
1841                }
1842            } finally {
1843                cancel(true /* interrupt if running */);
1844            }
1845            throw new OperationCanceledException();
1846        }
1847
1848        public Bundle getResult()
1849                throws OperationCanceledException, IOException, AuthenticatorException {
1850            return internalGetResult(null, null);
1851        }
1852
1853        public Bundle getResult(long timeout, TimeUnit unit)
1854                throws OperationCanceledException, IOException, AuthenticatorException {
1855            return internalGetResult(timeout, unit);
1856        }
1857
1858        protected void done() {
1859            if (mCallback != null) {
1860                postToHandler(mHandler, mCallback, this);
1861            }
1862        }
1863
1864        /** Handles the responses from the AccountManager */
1865        private class Response extends IAccountManagerResponse.Stub {
1866            public void onResult(Bundle bundle) {
1867                Intent intent = bundle.getParcelable(KEY_INTENT);
1868                if (intent != null && mActivity != null) {
1869                    // since the user provided an Activity we will silently start intents
1870                    // that we see
1871                    mActivity.startActivity(intent);
1872                    // leave the Future running to wait for the real response to this request
1873                } else if (bundle.getBoolean("retry")) {
1874                    try {
1875                        doWork();
1876                    } catch (RemoteException e) {
1877                        // this will only happen if the system process is dead, which means
1878                        // we will be dying ourselves
1879                    }
1880                } else {
1881                    set(bundle);
1882                }
1883            }
1884
1885            public void onError(int code, String message) {
1886                if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
1887                        || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
1888                    // the authenticator indicated that this request was canceled or we were
1889                    // forbidden to fulfill; cancel now
1890                    cancel(true /* mayInterruptIfRunning */);
1891                    return;
1892                }
1893                setException(convertErrorToException(code, message));
1894            }
1895        }
1896
1897    }
1898
1899    private abstract class BaseFutureTask<T> extends FutureTask<T> {
1900        final public IAccountManagerResponse mResponse;
1901        final Handler mHandler;
1902
1903        public BaseFutureTask(Handler handler) {
1904            super(new Callable<T>() {
1905                public T call() throws Exception {
1906                    throw new IllegalStateException("this should never be called");
1907                }
1908            });
1909            mHandler = handler;
1910            mResponse = new Response();
1911        }
1912
1913        public abstract void doWork() throws RemoteException;
1914
1915        public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
1916
1917        protected void postRunnableToHandler(Runnable runnable) {
1918            Handler handler = (mHandler == null) ? mMainHandler : mHandler;
1919            handler.post(runnable);
1920        }
1921
1922        protected void startTask() {
1923            try {
1924                doWork();
1925            } catch (RemoteException e) {
1926                setException(e);
1927            }
1928        }
1929
1930        protected class Response extends IAccountManagerResponse.Stub {
1931            public void onResult(Bundle bundle) {
1932                try {
1933                    T result = bundleToResult(bundle);
1934                    if (result == null) {
1935                        return;
1936                    }
1937                    set(result);
1938                    return;
1939                } catch (ClassCastException e) {
1940                    // we will set the exception below
1941                } catch (AuthenticatorException e) {
1942                    // we will set the exception below
1943                }
1944                onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
1945            }
1946
1947            public void onError(int code, String message) {
1948                if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
1949                        || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
1950                    // the authenticator indicated that this request was canceled or we were
1951                    // forbidden to fulfill; cancel now
1952                    cancel(true /* mayInterruptIfRunning */);
1953                    return;
1954                }
1955                setException(convertErrorToException(code, message));
1956            }
1957        }
1958    }
1959
1960    private abstract class Future2Task<T>
1961            extends BaseFutureTask<T> implements AccountManagerFuture<T> {
1962        final AccountManagerCallback<T> mCallback;
1963        public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
1964            super(handler);
1965            mCallback = callback;
1966        }
1967
1968        protected void done() {
1969            if (mCallback != null) {
1970                postRunnableToHandler(new Runnable() {
1971                    public void run() {
1972                        mCallback.run(Future2Task.this);
1973                    }
1974                });
1975            }
1976        }
1977
1978        public Future2Task<T> start() {
1979            startTask();
1980            return this;
1981        }
1982
1983        private T internalGetResult(Long timeout, TimeUnit unit)
1984                throws OperationCanceledException, IOException, AuthenticatorException {
1985            if (!isDone()) {
1986                ensureNotOnMainThread();
1987            }
1988            try {
1989                if (timeout == null) {
1990                    return get();
1991                } else {
1992                    return get(timeout, unit);
1993                }
1994            } catch (InterruptedException e) {
1995                // fall through and cancel
1996            } catch (TimeoutException e) {
1997                // fall through and cancel
1998            } catch (CancellationException e) {
1999                // fall through and cancel
2000            } catch (ExecutionException e) {
2001                final Throwable cause = e.getCause();
2002                if (cause instanceof IOException) {
2003                    throw (IOException) cause;
2004                } else if (cause instanceof UnsupportedOperationException) {
2005                    throw new AuthenticatorException(cause);
2006                } else if (cause instanceof AuthenticatorException) {
2007                    throw (AuthenticatorException) cause;
2008                } else if (cause instanceof RuntimeException) {
2009                    throw (RuntimeException) cause;
2010                } else if (cause instanceof Error) {
2011                    throw (Error) cause;
2012                } else {
2013                    throw new IllegalStateException(cause);
2014                }
2015            } finally {
2016                cancel(true /* interrupt if running */);
2017            }
2018            throw new OperationCanceledException();
2019        }
2020
2021        public T getResult()
2022                throws OperationCanceledException, IOException, AuthenticatorException {
2023            return internalGetResult(null, null);
2024        }
2025
2026        public T getResult(long timeout, TimeUnit unit)
2027                throws OperationCanceledException, IOException, AuthenticatorException {
2028            return internalGetResult(timeout, unit);
2029        }
2030
2031    }
2032
2033    private Exception convertErrorToException(int code, String message) {
2034        if (code == ERROR_CODE_NETWORK_ERROR) {
2035            return new IOException(message);
2036        }
2037
2038        if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
2039            return new UnsupportedOperationException(message);
2040        }
2041
2042        if (code == ERROR_CODE_INVALID_RESPONSE) {
2043            return new AuthenticatorException(message);
2044        }
2045
2046        if (code == ERROR_CODE_BAD_ARGUMENTS) {
2047            return new IllegalArgumentException(message);
2048        }
2049
2050        return new AuthenticatorException(message);
2051    }
2052
2053    private class GetAuthTokenByTypeAndFeaturesTask
2054            extends AmsTask implements AccountManagerCallback<Bundle> {
2055        GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
2056                final String[] features, Activity activityForPrompting,
2057                final Bundle addAccountOptions, final Bundle loginOptions,
2058                AccountManagerCallback<Bundle> callback, Handler handler) {
2059            super(activityForPrompting, handler, callback);
2060            if (accountType == null) throw new IllegalArgumentException("account type is null");
2061            mAccountType = accountType;
2062            mAuthTokenType = authTokenType;
2063            mFeatures = features;
2064            mAddAccountOptions = addAccountOptions;
2065            mLoginOptions = loginOptions;
2066            mMyCallback = this;
2067        }
2068        volatile AccountManagerFuture<Bundle> mFuture = null;
2069        final String mAccountType;
2070        final String mAuthTokenType;
2071        final String[] mFeatures;
2072        final Bundle mAddAccountOptions;
2073        final Bundle mLoginOptions;
2074        final AccountManagerCallback<Bundle> mMyCallback;
2075        private volatile int mNumAccounts = 0;
2076
2077        public void doWork() throws RemoteException {
2078            getAccountsByTypeAndFeatures(mAccountType, mFeatures,
2079                    new AccountManagerCallback<Account[]>() {
2080                        public void run(AccountManagerFuture<Account[]> future) {
2081                            Account[] accounts;
2082                            try {
2083                                accounts = future.getResult();
2084                            } catch (OperationCanceledException e) {
2085                                setException(e);
2086                                return;
2087                            } catch (IOException e) {
2088                                setException(e);
2089                                return;
2090                            } catch (AuthenticatorException e) {
2091                                setException(e);
2092                                return;
2093                            }
2094
2095                            mNumAccounts = accounts.length;
2096
2097                            if (accounts.length == 0) {
2098                                if (mActivity != null) {
2099                                    // no accounts, add one now. pretend that the user directly
2100                                    // made this request
2101                                    mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
2102                                            mAddAccountOptions, mActivity, mMyCallback, mHandler);
2103                                } else {
2104                                    // send result since we can't prompt to add an account
2105                                    Bundle result = new Bundle();
2106                                    result.putString(KEY_ACCOUNT_NAME, null);
2107                                    result.putString(KEY_ACCOUNT_TYPE, null);
2108                                    result.putString(KEY_AUTHTOKEN, null);
2109                                    try {
2110                                        mResponse.onResult(result);
2111                                    } catch (RemoteException e) {
2112                                        // this will never happen
2113                                    }
2114                                    // we are done
2115                                }
2116                            } else if (accounts.length == 1) {
2117                                // have a single account, return an authtoken for it
2118                                if (mActivity == null) {
2119                                    mFuture = getAuthToken(accounts[0], mAuthTokenType,
2120                                            false /* notifyAuthFailure */, mMyCallback, mHandler);
2121                                } else {
2122                                    mFuture = getAuthToken(accounts[0],
2123                                            mAuthTokenType, mLoginOptions,
2124                                            mActivity, mMyCallback, mHandler);
2125                                }
2126                            } else {
2127                                if (mActivity != null) {
2128                                    IAccountManagerResponse chooseResponse =
2129                                            new IAccountManagerResponse.Stub() {
2130                                        public void onResult(Bundle value) throws RemoteException {
2131                                            Account account = new Account(
2132                                                    value.getString(KEY_ACCOUNT_NAME),
2133                                                    value.getString(KEY_ACCOUNT_TYPE));
2134                                            mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
2135                                                    mActivity, mMyCallback, mHandler);
2136                                        }
2137
2138                                        public void onError(int errorCode, String errorMessage)
2139                                                throws RemoteException {
2140                                            mResponse.onError(errorCode, errorMessage);
2141                                        }
2142                                    };
2143                                    // have many accounts, launch the chooser
2144                                    Intent intent = new Intent();
2145                                    ComponentName componentName = ComponentName.unflattenFromString(
2146                                            Resources.getSystem().getString(
2147                                                    R.string.config_chooseAccountActivity));
2148                                    intent.setClassName(componentName.getPackageName(),
2149                                            componentName.getClassName());
2150                                    intent.putExtra(KEY_ACCOUNTS, accounts);
2151                                    intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
2152                                            new AccountManagerResponse(chooseResponse));
2153                                    mActivity.startActivity(intent);
2154                                    // the result will arrive via the IAccountManagerResponse
2155                                } else {
2156                                    // send result since we can't prompt to select an account
2157                                    Bundle result = new Bundle();
2158                                    result.putString(KEY_ACCOUNTS, null);
2159                                    try {
2160                                        mResponse.onResult(result);
2161                                    } catch (RemoteException e) {
2162                                        // this will never happen
2163                                    }
2164                                    // we are done
2165                                }
2166                            }
2167                        }}, mHandler);
2168        }
2169
2170        public void run(AccountManagerFuture<Bundle> future) {
2171            try {
2172                final Bundle result = future.getResult();
2173                if (mNumAccounts == 0) {
2174                    final String accountName = result.getString(KEY_ACCOUNT_NAME);
2175                    final String accountType = result.getString(KEY_ACCOUNT_TYPE);
2176                    if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
2177                        setException(new AuthenticatorException("account not in result"));
2178                        return;
2179                    }
2180                    final Account account = new Account(accountName, accountType);
2181                    mNumAccounts = 1;
2182                    getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
2183                            mMyCallback, mHandler);
2184                    return;
2185                }
2186                set(result);
2187            } catch (OperationCanceledException e) {
2188                cancel(true /* mayInterruptIfRUnning */);
2189            } catch (IOException e) {
2190                setException(e);
2191            } catch (AuthenticatorException e) {
2192                setException(e);
2193            }
2194        }
2195    }
2196
2197    /**
2198     * This convenience helper combines the functionality of
2199     * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and
2200     * {@link #addAccount}.
2201     *
2202     * <p>This method gets a list of the accounts matching the
2203     * specified type and feature set; if there is exactly one, it is
2204     * used; if there are more than one, the user is prompted to pick one;
2205     * if there are none, the user is prompted to add one.  Finally,
2206     * an auth token is acquired for the chosen account.
2207     *
2208     * <p>This method may be called from any thread, but the returned
2209     * {@link AccountManagerFuture} must not be used on the main thread.
2210     *
2211     * @param accountType The account type required
2212     *     (see {@link #getAccountsByType}), must not be null
2213     * @param authTokenType The desired auth token type
2214     *     (see {@link #getAuthToken}), must not be null
2215     * @param features Required features for the account
2216     *     (see {@link #getAccountsByTypeAndFeatures}), may be null or empty
2217     * @param activity The {@link Activity} context to use for launching new
2218     *     sub-Activities to prompt to add an account, select an account,
2219     *     and/or enter a password, as necessary; used only to call
2220     *     startActivity(); should not be null
2221     * @param addAccountOptions Authenticator-specific options to use for
2222     *     adding new accounts; may be null or empty
2223     * @param getAuthTokenOptions Authenticator-specific options to use for
2224     *     getting auth tokens; may be null or empty
2225     * @param callback Callback to invoke when the request completes,
2226     *     null for no callback
2227     * @param handler {@link Handler} identifying the callback thread,
2228     *     null for the main thread
2229     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
2230     *     at least the following fields:
2231     * <ul>
2232     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
2233     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
2234     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
2235     * </ul>
2236     *
2237     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
2238     * <ul>
2239     * <li> {@link AuthenticatorException} if no authenticator was registered for
2240     *      this account type or the authenticator failed to respond
2241     * <li> {@link OperationCanceledException} if the operation was canceled for
2242     *      any reason, including the user canceling any operation
2243     * <li> {@link IOException} if the authenticator experienced an I/O problem
2244     *      updating settings, usually because of network trouble
2245     * </ul>
2246     */
2247    public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
2248            final String accountType, final String authTokenType, final String[] features,
2249            final Activity activity, final Bundle addAccountOptions,
2250            final Bundle getAuthTokenOptions,
2251            final AccountManagerCallback<Bundle> callback, final Handler handler) {
2252        if (accountType == null) throw new IllegalArgumentException("account type is null");
2253        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
2254        final GetAuthTokenByTypeAndFeaturesTask task =
2255                new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
2256                activity, addAccountOptions, getAuthTokenOptions, callback, handler);
2257        task.start();
2258        return task;
2259    }
2260
2261    /**
2262     * Returns an intent to an {@link Activity} that prompts the user to choose from a list of
2263     * accounts.
2264     * The caller will then typically start the activity by calling
2265     * <code>startActivityForResult(intent, ...);</code>.
2266     * <p>
2267     * On success the activity returns a Bundle with the account name and type specified using
2268     * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
2269     * <p>
2270     * The most common case is to call this with one account type, e.g.:
2271     * <p>
2272     * <pre>  newChooseAccountIntent(null, null, new String[]{"com.google"}, false, null,
2273     * null, null, null);</pre>
2274     * @param selectedAccount if specified, indicates that the {@link Account} is the currently
2275     * selected one, according to the caller's definition of selected.
2276     * @param allowableAccounts an optional {@link ArrayList} of accounts that are allowed to be
2277     * shown. If not specified then this field will not limit the displayed accounts.
2278     * @param allowableAccountTypes an optional string array of account types. These are used
2279     * both to filter the shown accounts and to filter the list of account types that are shown
2280     * when adding an account. If not specified then this field will not limit the displayed
2281     * account types when adding an account.
2282     * @param alwaysPromptForAccount if set the account chooser screen is always shown, otherwise
2283     * it is only shown when there is more than one account from which to choose
2284     * @param descriptionOverrideText if non-null this string is used as the description in the
2285     * accounts chooser screen rather than the default
2286     * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
2287     * authTokenType parameter
2288     * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
2289     * requiredFeatures parameter
2290     * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
2291     * parameter
2292     * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
2293     */
2294    static public Intent newChooseAccountIntent(Account selectedAccount,
2295            ArrayList<Account> allowableAccounts,
2296            String[] allowableAccountTypes,
2297            boolean alwaysPromptForAccount,
2298            String descriptionOverrideText,
2299            String addAccountAuthTokenType,
2300            String[] addAccountRequiredFeatures,
2301            Bundle addAccountOptions) {
2302        Intent intent = new Intent();
2303        ComponentName componentName = ComponentName.unflattenFromString(
2304                Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
2305        intent.setClassName(componentName.getPackageName(),
2306                componentName.getClassName());
2307        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
2308                allowableAccounts);
2309        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
2310                allowableAccountTypes);
2311        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
2312                addAccountOptions);
2313        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount);
2314        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT,
2315                alwaysPromptForAccount);
2316        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE,
2317                descriptionOverrideText);
2318        intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
2319                addAccountAuthTokenType);
2320        intent.putExtra(
2321                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
2322                addAccountRequiredFeatures);
2323        return intent;
2324    }
2325
2326    private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
2327            Maps.newHashMap();
2328
2329    /**
2330     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
2331     * so that it can read the updated list of accounts and send them to the listener
2332     * in mAccountsUpdatedListeners.
2333     */
2334    private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
2335        public void onReceive(final Context context, final Intent intent) {
2336            final Account[] accounts = getAccounts();
2337            // send the result to the listeners
2338            synchronized (mAccountsUpdatedListeners) {
2339                for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
2340                        mAccountsUpdatedListeners.entrySet()) {
2341                    postToHandler(entry.getValue(), entry.getKey(), accounts);
2342                }
2343            }
2344        }
2345    };
2346
2347    /**
2348     * Adds an {@link OnAccountsUpdateListener} to this instance of the
2349     * {@link AccountManager}.  This listener will be notified whenever the
2350     * list of accounts on the device changes.
2351     *
2352     * <p>As long as this listener is present, the AccountManager instance
2353     * will not be garbage-collected, and neither will the {@link Context}
2354     * used to retrieve it, which may be a large Activity instance.  To avoid
2355     * memory leaks, you must remove this listener before then.  Normally
2356     * listeners are added in an Activity or Service's {@link Activity#onCreate}
2357     * and removed in {@link Activity#onDestroy}.
2358     *
2359     * <p>It is safe to call this method from the main thread.
2360     *
2361     * <p>This method requires the caller to hold the permission
2362     * {@link android.Manifest.permission#GET_ACCOUNTS}.
2363     *
2364     * @param listener The listener to send notifications to
2365     * @param handler {@link Handler} identifying the thread to use
2366     *     for notifications, null for the main thread
2367     * @param updateImmediately If true, the listener will be invoked
2368     *     (on the handler thread) right away with the current account list
2369     * @throws IllegalArgumentException if listener is null
2370     * @throws IllegalStateException if listener was already added
2371     */
2372    @RequiresPermission(GET_ACCOUNTS)
2373    public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
2374            Handler handler, boolean updateImmediately) {
2375        if (listener == null) {
2376            throw new IllegalArgumentException("the listener is null");
2377        }
2378        synchronized (mAccountsUpdatedListeners) {
2379            if (mAccountsUpdatedListeners.containsKey(listener)) {
2380                throw new IllegalStateException("this listener is already added");
2381            }
2382            final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
2383
2384            mAccountsUpdatedListeners.put(listener, handler);
2385
2386            if (wasEmpty) {
2387                // Register a broadcast receiver to monitor account changes
2388                IntentFilter intentFilter = new IntentFilter();
2389                intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
2390                // To recover from disk-full.
2391                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
2392                mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
2393            }
2394        }
2395
2396        if (updateImmediately) {
2397            postToHandler(handler, listener, getAccounts());
2398        }
2399    }
2400
2401    /**
2402     * Removes an {@link OnAccountsUpdateListener} previously registered with
2403     * {@link #addOnAccountsUpdatedListener}.  The listener will no longer
2404     * receive notifications of account changes.
2405     *
2406     * <p>It is safe to call this method from the main thread.
2407     *
2408     * <p>No permission is required to call this method.
2409     *
2410     * @param listener The previously added listener to remove
2411     * @throws IllegalArgumentException if listener is null
2412     * @throws IllegalStateException if listener was not already added
2413     */
2414    public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
2415        if (listener == null) throw new IllegalArgumentException("listener is null");
2416        synchronized (mAccountsUpdatedListeners) {
2417            if (!mAccountsUpdatedListeners.containsKey(listener)) {
2418                Log.e(TAG, "Listener was not previously added");
2419                return;
2420            }
2421            mAccountsUpdatedListeners.remove(listener);
2422            if (mAccountsUpdatedListeners.isEmpty()) {
2423                mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
2424            }
2425        }
2426    }
2427}
2428