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