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