Home | History | Annotate | Download | only in accounts
      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.Intent;
     21 import android.content.Context;
     22 import android.content.IntentFilter;
     23 import android.content.BroadcastReceiver;
     24 import android.database.SQLException;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Looper;
     28 import android.os.RemoteException;
     29 import android.os.Parcelable;
     30 import android.os.Build;
     31 import android.util.Log;
     32 import android.text.TextUtils;
     33 
     34 import java.io.IOException;
     35 import java.util.concurrent.Callable;
     36 import java.util.concurrent.CancellationException;
     37 import java.util.concurrent.ExecutionException;
     38 import java.util.concurrent.FutureTask;
     39 import java.util.concurrent.TimeoutException;
     40 import java.util.concurrent.TimeUnit;
     41 import java.util.HashMap;
     42 import java.util.Map;
     43 
     44 import com.google.android.collect.Maps;
     45 
     46 /**
     47  * This class provides access to a centralized registry of the user's
     48  * online accounts.  The user enters credentials (username and password) once
     49  * per account, granting applications access to online resources with
     50  * "one-click" approval.
     51  *
     52  * <p>Different online services have different ways of handling accounts and
     53  * authentication, so the account manager uses pluggable <em>authenticator</em>
     54  * modules for different <em>account types</em>.  Authenticators (which may be
     55  * written by third parties) handle the actual details of validating account
     56  * credentials and storing account information.  For example, Google, Facebook,
     57  * and Microsoft Exchange each have their own authenticator.
     58  *
     59  * <p>Many servers support some notion of an <em>authentication token</em>,
     60  * which can be used to authenticate a request to the server without sending
     61  * the user's actual password.  (Auth tokens are normally created with a
     62  * separate request which does include the user's credentials.)  AccountManager
     63  * can generate auth tokens for applications, so the application doesn't need to
     64  * handle passwords directly.  Auth tokens are normally reusable and cached by
     65  * AccountManager, but must be refreshed periodically.  It's the responsibility
     66  * of applications to <em>invalidate</em> auth tokens when they stop working so
     67  * the AccountManager knows it needs to regenerate them.
     68  *
     69  * <p>Applications accessing a server normally go through these steps:
     70  *
     71  * <ul>
     72  * <li>Get an instance of AccountManager using {@link #get(Context)}.
     73  *
     74  * <li>List the available accounts using {@link #getAccountsByType} or
     75  * {@link #getAccountsByTypeAndFeatures}.  Normally applications will only
     76  * be interested in accounts with one particular <em>type</em>, which
     77  * identifies the authenticator.  Account <em>features</em> are used to
     78  * identify particular account subtypes and capabilities.  Both the account
     79  * type and features are authenticator-specific strings, and must be known by
     80  * the application in coordination with its preferred authenticators.
     81  *
     82  * <li>Select one or more of the available accounts, possibly by asking the
     83  * user for their preference.  If no suitable accounts are available,
     84  * {@link #addAccount} may be called to prompt the user to create an
     85  * account of the appropriate type.
     86  *
     87  * <li><b>Important:</b> If the application is using a previously remembered
     88  * account selection, it must make sure the account is still in the list
     89  * of accounts returned by {@link #getAccountsByType}.  Requesting an auth token
     90  * for an account no longer on the device results in an undefined failure.
     91  *
     92  * <li>Request an auth token for the selected account(s) using one of the
     93  * {@link #getAuthToken} methods or related helpers.  Refer to the description
     94  * of each method for exact usage and error handling details.
     95  *
     96  * <li>Make the request using the auth token.  The form of the auth token,
     97  * the format of the request, and the protocol used are all specific to the
     98  * service you are accessing.  The application may use whatever network and
     99  * protocol libraries are useful.
    100  *
    101  * <li><b>Important:</b> If the request fails with an authentication error,
    102  * it could be that a cached auth token is stale and no longer honored by
    103  * the server.  The application must call {@link #invalidateAuthToken} to remove
    104  * the token from the cache, otherwise requests will continue failing!  After
    105  * invalidating the auth token, immediately go back to the "Request an auth
    106  * token" step above.  If the process fails the second time, then it can be
    107  * treated as a "genuine" authentication failure and the user notified or other
    108  * appropriate actions taken.
    109  * </ul>
    110  *
    111  * <p>Some AccountManager methods may need to interact with the user to
    112  * prompt for credentials, present options, or ask the user to add an account.
    113  * The caller may choose whether to allow AccountManager to directly launch the
    114  * necessary user interface and wait for the user, or to return an Intent which
    115  * the caller may use to launch the interface, or (in some cases) to install a
    116  * notification which the user can select at any time to launch the interface.
    117  * To have AccountManager launch the interface directly, the caller must supply
    118  * the current foreground {@link Activity} context.
    119  *
    120  * <p>Many AccountManager methods take {@link AccountManagerCallback} and
    121  * {@link Handler} as parameters.  These methods return immediately and
    122  * run asynchronously. If a callback is provided then
    123  * {@link AccountManagerCallback#run} will be invoked on the Handler's
    124  * thread when the request completes, successfully or not.
    125  * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
    126  * on the {@link AccountManagerFuture} returned by the method (and also passed
    127  * to the callback).  This method waits for the operation to complete (if
    128  * necessary) and either returns the result or throws an exception if an error
    129  * occurred during the operation.  To make the request synchronously, call
    130  * {@link AccountManagerFuture#getResult()} immediately on receiving the
    131  * future from the method; no callback need be supplied.
    132  *
    133  * <p>Requests which may block, including
    134  * {@link AccountManagerFuture#getResult()}, must never be called on
    135  * the application's main event thread.  These operations throw
    136  * {@link IllegalStateException} if they are used on the main thread.
    137  */
    138 public class AccountManager {
    139     private static final String TAG = "AccountManager";
    140 
    141     public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
    142     public static final int ERROR_CODE_NETWORK_ERROR = 3;
    143     public static final int ERROR_CODE_CANCELED = 4;
    144     public static final int ERROR_CODE_INVALID_RESPONSE = 5;
    145     public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
    146     public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
    147     public static final int ERROR_CODE_BAD_REQUEST = 8;
    148 
    149     /**
    150      * Bundle key used for the {@link String} account name in results
    151      * from methods which return information about a particular account.
    152      */
    153     public static final String KEY_ACCOUNT_NAME = "authAccount";
    154 
    155     /**
    156      * Bundle key used for the {@link String} account type in results
    157      * from methods which return information about a particular account.
    158      */
    159     public static final String KEY_ACCOUNT_TYPE = "accountType";
    160 
    161     /**
    162      * Bundle key used for the auth token value in results
    163      * from {@link #getAuthToken} and friends.
    164      */
    165     public static final String KEY_AUTHTOKEN = "authtoken";
    166 
    167     /**
    168      * Bundle key used for an {@link Intent} in results from methods that
    169      * may require the caller to interact with the user.  The Intent can
    170      * be used to start the corresponding user interface activity.
    171      */
    172     public static final String KEY_INTENT = "intent";
    173 
    174     /**
    175      * Bundle key used to supply the password directly in options to
    176      * {@link #confirmCredentials}, rather than prompting the user with
    177      * the standard password prompt.
    178      */
    179     public static final String KEY_PASSWORD = "password";
    180 
    181     public static final String KEY_ACCOUNTS = "accounts";
    182     public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
    183     public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
    184     public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
    185     public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
    186     public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
    187     public static final String KEY_BOOLEAN_RESULT = "booleanResult";
    188     public static final String KEY_ERROR_CODE = "errorCode";
    189     public static final String KEY_ERROR_MESSAGE = "errorMessage";
    190     public static final String KEY_USERDATA = "userdata";
    191     /**
    192      * Authenticators using 'customTokens' option will also get the UID of the
    193      * caller
    194      * @hide
    195      */
    196     public static final String KEY_CALLER_UID = "callerUid";
    197 
    198     /**
    199      * @hide
    200      */
    201     public static final String KEY_CALLER_PID = "callerPid";
    202 
    203     /**
    204      * Boolean, if set and 'customTokens' the authenticator is responsible for
    205      * notifications.
    206      * @hide
    207      */
    208     public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
    209 
    210     public static final String ACTION_AUTHENTICATOR_INTENT =
    211             "android.accounts.AccountAuthenticator";
    212     public static final String AUTHENTICATOR_META_DATA_NAME =
    213             "android.accounts.AccountAuthenticator";
    214     public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
    215 
    216     private final Context mContext;
    217     private final IAccountManager mService;
    218     private final Handler mMainHandler;
    219 
    220     /**
    221      * Action sent as a broadcast Intent by the AccountsService
    222      * when accounts are added, accounts are removed, or an
    223      * account's credentials (saved password, etc) are changed.
    224      *
    225      * @see #addOnAccountsUpdatedListener
    226      */
    227     public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
    228         "android.accounts.LOGIN_ACCOUNTS_CHANGED";
    229 
    230     /**
    231      * @hide
    232      */
    233     public AccountManager(Context context, IAccountManager service) {
    234         mContext = context;
    235         mService = service;
    236         mMainHandler = new Handler(mContext.getMainLooper());
    237     }
    238 
    239     /**
    240      * @hide used for testing only
    241      */
    242     public AccountManager(Context context, IAccountManager service, Handler handler) {
    243         mContext = context;
    244         mService = service;
    245         mMainHandler = handler;
    246     }
    247 
    248     /**
    249      * @hide for internal use only
    250      */
    251     public static Bundle sanitizeResult(Bundle result) {
    252         if (result != null) {
    253             if (result.containsKey(KEY_AUTHTOKEN)
    254                     && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
    255                 final Bundle newResult = new Bundle(result);
    256                 newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
    257                 return newResult;
    258             }
    259         }
    260         return result;
    261     }
    262 
    263     /**
    264      * Gets an AccountManager instance associated with a Context.
    265      * The {@link Context} will be used as long as the AccountManager is
    266      * active, so make sure to use a {@link Context} whose lifetime is
    267      * commensurate with any listeners registered to
    268      * {@link #addOnAccountsUpdatedListener} or similar methods.
    269      *
    270      * <p>It is safe to call this method from the main thread.
    271      *
    272      * <p>No permission is required to call this method.
    273      *
    274      * @param context The {@link Context} to use when necessary
    275      * @return An {@link AccountManager} instance
    276      */
    277     public static AccountManager get(Context context) {
    278         if (context == null) throw new IllegalArgumentException("context is null");
    279         return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
    280     }
    281 
    282     /**
    283      * Gets the saved password associated with the account.
    284      * This is intended for authenticators and related code; applications
    285      * should get an auth token instead.
    286      *
    287      * <p>It is safe to call this method from the main thread.
    288      *
    289      * <p>This method requires the caller to hold the permission
    290      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    291      * and to have the same UID as the account's authenticator.
    292      *
    293      * @param account The account to query for a password
    294      * @return The account's password, null if none or if the account doesn't exist
    295      */
    296     public String getPassword(final Account account) {
    297         if (account == null) throw new IllegalArgumentException("account is null");
    298         try {
    299             return mService.getPassword(account);
    300         } catch (RemoteException e) {
    301             // will never happen
    302             throw new RuntimeException(e);
    303         }
    304     }
    305 
    306     /**
    307      * Gets the user data named by "key" associated with the account.
    308      * This is intended for authenticators and related code to store
    309      * arbitrary metadata along with accounts.  The meaning of the keys
    310      * and values is up to the authenticator for the account.
    311      *
    312      * <p>It is safe to call this method from the main thread.
    313      *
    314      * <p>This method requires the caller to hold the permission
    315      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    316      * and to have the same UID as the account's authenticator.
    317      *
    318      * @param account The account to query for user data
    319      * @return The user data, null if the account or key doesn't exist
    320      */
    321     public String getUserData(final Account account, final String key) {
    322         if (account == null) throw new IllegalArgumentException("account is null");
    323         if (key == null) throw new IllegalArgumentException("key is null");
    324         try {
    325             return mService.getUserData(account, key);
    326         } catch (RemoteException e) {
    327             // will never happen
    328             throw new RuntimeException(e);
    329         }
    330     }
    331 
    332     /**
    333      * Lists the currently registered authenticators.
    334      *
    335      * <p>It is safe to call this method from the main thread.
    336      *
    337      * <p>No permission is required to call this method.
    338      *
    339      * @return An array of {@link AuthenticatorDescription} for every
    340      *     authenticator known to the AccountManager service.  Empty (never
    341      *     null) if no authenticators are known.
    342      */
    343     public AuthenticatorDescription[] getAuthenticatorTypes() {
    344         try {
    345             return mService.getAuthenticatorTypes();
    346         } catch (RemoteException e) {
    347             // will never happen
    348             throw new RuntimeException(e);
    349         }
    350     }
    351 
    352     /**
    353      * Lists all accounts of any type registered on the device.
    354      * Equivalent to getAccountsByType(null).
    355      *
    356      * <p>It is safe to call this method from the main thread.
    357      *
    358      * <p>This method requires the caller to hold the permission
    359      * {@link android.Manifest.permission#GET_ACCOUNTS}.
    360      *
    361      * @return An array of {@link Account}, one for each account.  Empty
    362      *     (never null) if no accounts have been added.
    363      */
    364     public Account[] getAccounts() {
    365         try {
    366             return mService.getAccounts(null);
    367         } catch (RemoteException e) {
    368             // won't ever happen
    369             throw new RuntimeException(e);
    370         }
    371     }
    372 
    373     /**
    374      * Lists all accounts of a particular type.  The account type is a
    375      * string token corresponding to the authenticator and useful domain
    376      * of the account.  For example, there are types corresponding to Google
    377      * and Facebook.  The exact string token to use will be published somewhere
    378      * associated with the authenticator in question.
    379      *
    380      * <p>It is safe to call this method from the main thread.
    381      *
    382      * <p>This method requires the caller to hold the permission
    383      * {@link android.Manifest.permission#GET_ACCOUNTS}.
    384      *
    385      * @param type The type of accounts to return, null to retrieve all accounts
    386      * @return An array of {@link Account}, one per matching account.  Empty
    387      *     (never null) if no accounts of the specified type have been added.
    388      */
    389     public Account[] getAccountsByType(String type) {
    390         try {
    391             return mService.getAccounts(type);
    392         } catch (RemoteException e) {
    393             // won't ever happen
    394             throw new RuntimeException(e);
    395         }
    396     }
    397 
    398     /**
    399      * Finds out whether a particular account has all the specified features.
    400      * Account features are authenticator-specific string tokens identifying
    401      * boolean account properties.  For example, features are used to tell
    402      * whether Google accounts have a particular service (such as Google
    403      * Calendar or Google Talk) enabled.  The feature names and their meanings
    404      * are published somewhere associated with the authenticator in question.
    405      *
    406      * <p>This method may be called from any thread, but the returned
    407      * {@link AccountManagerFuture} must not be used on the main thread.
    408      *
    409      * <p>This method requires the caller to hold the permission
    410      * {@link android.Manifest.permission#GET_ACCOUNTS}.
    411      *
    412      * @param account The {@link Account} to test
    413      * @param features An array of the account features to check
    414      * @param callback Callback to invoke when the request completes,
    415      *     null for no callback
    416      * @param handler {@link Handler} identifying the callback thread,
    417      *     null for the main thread
    418      * @return An {@link AccountManagerFuture} which resolves to a Boolean,
    419      * true if the account exists and has all of the specified features.
    420      */
    421     public AccountManagerFuture<Boolean> hasFeatures(final Account account,
    422             final String[] features,
    423             AccountManagerCallback<Boolean> callback, Handler handler) {
    424         if (account == null) throw new IllegalArgumentException("account is null");
    425         if (features == null) throw new IllegalArgumentException("features is null");
    426         return new Future2Task<Boolean>(handler, callback) {
    427             public void doWork() throws RemoteException {
    428                 mService.hasFeatures(mResponse, account, features);
    429             }
    430             public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
    431                 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
    432                     throw new AuthenticatorException("no result in response");
    433                 }
    434                 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
    435             }
    436         }.start();
    437     }
    438 
    439     /**
    440      * Lists all accounts of a type which have certain features.  The account
    441      * type identifies the authenticator (see {@link #getAccountsByType}).
    442      * Account features are authenticator-specific string tokens identifying
    443      * boolean account properties (see {@link #hasFeatures}).
    444      *
    445      * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator,
    446      * which may contact the server or do other work to check account features,
    447      * so the method returns an {@link AccountManagerFuture}.
    448      *
    449      * <p>This method may be called from any thread, but the returned
    450      * {@link AccountManagerFuture} must not be used on the main thread.
    451      *
    452      * <p>This method requires the caller to hold the permission
    453      * {@link android.Manifest.permission#GET_ACCOUNTS}.
    454      *
    455      * @param type The type of accounts to return, must not be null
    456      * @param features An array of the account features to require,
    457      *     may be null or empty
    458      * @param callback Callback to invoke when the request completes,
    459      *     null for no callback
    460      * @param handler {@link Handler} identifying the callback thread,
    461      *     null for the main thread
    462      * @return An {@link AccountManagerFuture} which resolves to an array of
    463      *     {@link Account}, one per account of the specified type which
    464      *     matches the requested features.
    465      */
    466     public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
    467             final String type, final String[] features,
    468             AccountManagerCallback<Account[]> callback, Handler handler) {
    469         if (type == null) throw new IllegalArgumentException("type is null");
    470         return new Future2Task<Account[]>(handler, callback) {
    471             public void doWork() throws RemoteException {
    472                 mService.getAccountsByFeatures(mResponse, type, features);
    473             }
    474             public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
    475                 if (!bundle.containsKey(KEY_ACCOUNTS)) {
    476                     throw new AuthenticatorException("no result in response");
    477                 }
    478                 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
    479                 Account[] descs = new Account[parcelables.length];
    480                 for (int i = 0; i < parcelables.length; i++) {
    481                     descs[i] = (Account) parcelables[i];
    482                 }
    483                 return descs;
    484             }
    485         }.start();
    486     }
    487 
    488     /**
    489      * Adds an account directly to the AccountManager.  Normally used by sign-up
    490      * wizards associated with authenticators, not directly by applications.
    491      *
    492      * <p>It is safe to call this method from the main thread.
    493      *
    494      * <p>This method requires the caller to hold the permission
    495      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    496      * and to have the same UID as the added account's authenticator.
    497      *
    498      * @param account The {@link Account} to add
    499      * @param password The password to associate with the account, null for none
    500      * @param userdata String values to use for the account's userdata, null for none
    501      * @return True if the account was successfully added, false if the account
    502      *     already exists, the account is null, or another error occurs.
    503      */
    504     public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
    505         if (account == null) throw new IllegalArgumentException("account is null");
    506         try {
    507             return mService.addAccount(account, password, userdata);
    508         } catch (RemoteException e) {
    509             // won't ever happen
    510             throw new RuntimeException(e);
    511         }
    512     }
    513 
    514     /**
    515      * Removes an account from the AccountManager.  Does nothing if the account
    516      * does not exist.  Does not delete the account from the server.
    517      * The authenticator may have its own policies preventing account
    518      * deletion, in which case the account will not be deleted.
    519      *
    520      * <p>This method may be called from any thread, but the returned
    521      * {@link AccountManagerFuture} must not be used on the main thread.
    522      *
    523      * <p>This method requires the caller to hold the permission
    524      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
    525      *
    526      * @param account The {@link Account} to remove
    527      * @param callback Callback to invoke when the request completes,
    528      *     null for no callback
    529      * @param handler {@link Handler} identifying the callback thread,
    530      *     null for the main thread
    531      * @return An {@link AccountManagerFuture} which resolves to a Boolean,
    532      *     true if the account has been successfully removed,
    533      *     false if the authenticator forbids deleting this account.
    534      */
    535     public AccountManagerFuture<Boolean> removeAccount(final Account account,
    536             AccountManagerCallback<Boolean> callback, Handler handler) {
    537         if (account == null) throw new IllegalArgumentException("account is null");
    538         return new Future2Task<Boolean>(handler, callback) {
    539             public void doWork() throws RemoteException {
    540                 mService.removeAccount(mResponse, account);
    541             }
    542             public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
    543                 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
    544                     throw new AuthenticatorException("no result in response");
    545                 }
    546                 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
    547             }
    548         }.start();
    549     }
    550 
    551     /**
    552      * Removes an auth token from the AccountManager's cache.  Does nothing if
    553      * the auth token is not currently in the cache.  Applications must call this
    554      * method when the auth token is found to have expired or otherwise become
    555      * invalid for authenticating requests.  The AccountManager does not validate
    556      * or expire cached auth tokens otherwise.
    557      *
    558      * <p>It is safe to call this method from the main thread.
    559      *
    560      * <p>This method requires the caller to hold the permission
    561      * {@link android.Manifest.permission#MANAGE_ACCOUNTS} or
    562      * {@link android.Manifest.permission#USE_CREDENTIALS}
    563      *
    564      * @param accountType The account type of the auth token to invalidate, must not be null
    565      * @param authToken The auth token to invalidate, may be null
    566      */
    567     public void invalidateAuthToken(final String accountType, final String authToken) {
    568         if (accountType == null) throw new IllegalArgumentException("accountType is null");
    569         try {
    570             if (authToken != null) {
    571                 mService.invalidateAuthToken(accountType, authToken);
    572             }
    573         } catch (RemoteException e) {
    574             // won't ever happen
    575             throw new RuntimeException(e);
    576         }
    577     }
    578 
    579     /**
    580      * Gets an auth token from the AccountManager's cache.  If no auth
    581      * token is cached for this account, null will be returned -- a new
    582      * auth token will not be generated, and the server will not be contacted.
    583      * Intended for use by the authenticator, not directly by applications.
    584      *
    585      * <p>It is safe to call this method from the main thread.
    586      *
    587      * <p>This method requires the caller to hold the permission
    588      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    589      * and to have the same UID as the account's authenticator.
    590      *
    591      * @param account The account to fetch an auth token for
    592      * @param authTokenType The type of auth token to fetch, see {#getAuthToken}
    593      * @return The cached auth token for this account and type, or null if
    594      *     no auth token is cached or the account does not exist.
    595      */
    596     public String peekAuthToken(final Account account, final String authTokenType) {
    597         if (account == null) throw new IllegalArgumentException("account is null");
    598         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    599         try {
    600             return mService.peekAuthToken(account, authTokenType);
    601         } catch (RemoteException e) {
    602             // won't ever happen
    603             throw new RuntimeException(e);
    604         }
    605     }
    606 
    607     /**
    608      * Sets or forgets a saved password.  This modifies the local copy of the
    609      * password used to automatically authenticate the user; it does
    610      * not change the user's account password on the server.  Intended for use
    611      * by the authenticator, not directly by applications.
    612      *
    613      * <p>It is safe to call this method from the main thread.
    614      *
    615      * <p>This method requires the caller to hold the permission
    616      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    617      * and have the same UID as the account's authenticator.
    618      *
    619      * @param account The account to set a password for
    620      * @param password The password to set, null to clear the password
    621      */
    622     public void setPassword(final Account account, final String password) {
    623         if (account == null) throw new IllegalArgumentException("account is null");
    624         try {
    625             mService.setPassword(account, password);
    626         } catch (RemoteException e) {
    627             // won't ever happen
    628             throw new RuntimeException(e);
    629         }
    630     }
    631 
    632     /**
    633      * Forgets a saved password.  This erases the local copy of the password;
    634      * it does not change the user's account password on the server.
    635      * Has the same effect as setPassword(account, null) but requires fewer
    636      * permissions, and may be used by applications or management interfaces
    637      * to "sign out" from an account.
    638      *
    639      * <p>It is safe to call this method from the main thread.
    640      *
    641      * <p>This method requires the caller to hold the permission
    642      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}
    643      *
    644      * @param account The account whose password to clear
    645      */
    646     public void clearPassword(final Account account) {
    647         if (account == null) throw new IllegalArgumentException("account is null");
    648         try {
    649             mService.clearPassword(account);
    650         } catch (RemoteException e) {
    651             // won't ever happen
    652             throw new RuntimeException(e);
    653         }
    654     }
    655 
    656     /**
    657      * Sets one userdata key for an account.  Intended by use for the
    658      * authenticator to stash state for itself, not directly by applications.
    659      * The meaning of the keys and values is up to the authenticator.
    660      *
    661      * <p>It is safe to call this method from the main thread.
    662      *
    663      * <p>This method requires the caller to hold the permission
    664      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    665      * and to have the same UID as the account's authenticator.
    666      *
    667      * @param account The account to set the userdata for
    668      * @param key The userdata key to set.  Must not be null
    669      * @param value The value to set, null to clear this userdata key
    670      */
    671     public void setUserData(final Account account, final String key, final String value) {
    672         if (account == null) throw new IllegalArgumentException("account is null");
    673         if (key == null) throw new IllegalArgumentException("key is null");
    674         try {
    675             mService.setUserData(account, key, value);
    676         } catch (RemoteException e) {
    677             // won't ever happen
    678             throw new RuntimeException(e);
    679         }
    680     }
    681 
    682     /**
    683      * Adds an auth token to the AccountManager cache for an account.
    684      * If the account does not exist then this call has no effect.
    685      * Replaces any previous auth token for this account and auth token type.
    686      * Intended for use by the authenticator, not directly by applications.
    687      *
    688      * <p>It is safe to call this method from the main thread.
    689      *
    690      * <p>This method requires the caller to hold the permission
    691      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
    692      * and to have the same UID as the account's authenticator.
    693      *
    694      * @param account The account to set an auth token for
    695      * @param authTokenType The type of the auth token, see {#getAuthToken}
    696      * @param authToken The auth token to add to the cache
    697      */
    698     public void setAuthToken(Account account, final String authTokenType, final String authToken) {
    699         if (account == null) throw new IllegalArgumentException("account is null");
    700         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    701         try {
    702             mService.setAuthToken(account, authTokenType, authToken);
    703         } catch (RemoteException e) {
    704             // won't ever happen
    705             throw new RuntimeException(e);
    706         }
    707     }
    708 
    709     /**
    710      * This convenience helper synchronously gets an auth token with
    711      * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
    712      *
    713      * <p>This method may block while a network request completes, and must
    714      * never be made from the main thread.
    715      *
    716      * <p>This method requires the caller to hold the permission
    717      * {@link android.Manifest.permission#USE_CREDENTIALS}.
    718      *
    719      * @param account The account to fetch an auth token for
    720      * @param authTokenType The auth token type, see {#link getAuthToken}
    721      * @param notifyAuthFailure If true, display a notification and return null
    722      *     if authentication fails; if false, prompt and wait for the user to
    723      *     re-enter correct credentials before returning
    724      * @return An auth token of the specified type for this account, or null
    725      *     if authentication fails or none can be fetched.
    726      * @throws AuthenticatorException if the authenticator failed to respond
    727      * @throws OperationCanceledException if the request was canceled for any
    728      *     reason, including the user canceling a credential request
    729      * @throws java.io.IOException if the authenticator experienced an I/O problem
    730      *     creating a new auth token, usually because of network trouble
    731      */
    732     public String blockingGetAuthToken(Account account, String authTokenType,
    733             boolean notifyAuthFailure)
    734             throws OperationCanceledException, IOException, AuthenticatorException {
    735         if (account == null) throw new IllegalArgumentException("account is null");
    736         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    737         Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
    738                 null /* handler */).getResult();
    739         if (bundle == null) {
    740             // This should never happen, but it does, occasionally. If it does return null to
    741             // signify that we were not able to get the authtoken.
    742             // TODO: remove this when the bug is found that sometimes causes a null bundle to be
    743             // returned
    744             Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
    745                     + account + ", authTokenType " + authTokenType);
    746             return null;
    747         }
    748         return bundle.getString(KEY_AUTHTOKEN);
    749     }
    750 
    751     /**
    752      * Gets an auth token of the specified type for a particular account,
    753      * prompting the user for credentials if necessary.  This method is
    754      * intended for applications running in the foreground where it makes
    755      * sense to ask the user directly for a password.
    756      *
    757      * <p>If a previously generated auth token is cached for this account and
    758      * type, then it is returned.  Otherwise, if a saved password is
    759      * available, it is sent to the server to generate a new auth token.
    760      * Otherwise, the user is prompted to enter a password.
    761      *
    762      * <p>Some authenticators have auth token <em>types</em>, whose value
    763      * is authenticator-dependent.  Some services use different token types to
    764      * access different functionality -- for example, Google uses different auth
    765      * tokens to access Gmail and Google Calendar for the same account.
    766      *
    767      * <p>This method may be called from any thread, but the returned
    768      * {@link AccountManagerFuture} must not be used on the main thread.
    769      *
    770      * <p>This method requires the caller to hold the permission
    771      * {@link android.Manifest.permission#USE_CREDENTIALS}.
    772      *
    773      * @param account The account to fetch an auth token for
    774      * @param authTokenType The auth token type, an authenticator-dependent
    775      *     string token, must not be null
    776      * @param options Authenticator-specific options for the request,
    777      *     may be null or empty
    778      * @param activity The {@link Activity} context to use for launching a new
    779      *     authenticator-defined sub-Activity to prompt the user for a password
    780      *     if necessary; used only to call startActivity(); must not be null.
    781      * @param callback Callback to invoke when the request completes,
    782      *     null for no callback
    783      * @param handler {@link Handler} identifying the callback thread,
    784      *     null for the main thread
    785      * @return An {@link AccountManagerFuture} which resolves to a Bundle with
    786      *     at least the following fields:
    787      * <ul>
    788      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
    789      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
    790      * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
    791      * </ul>
    792      *
    793      * (Other authenticator-specific values may be returned.)  If an auth token
    794      * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
    795      * <ul>
    796      * <li> {@link AuthenticatorException} if the authenticator failed to respond
    797      * <li> {@link OperationCanceledException} if the operation is canceled for
    798      *      any reason, incluidng the user canceling a credential request
    799      * <li> {@link IOException} if the authenticator experienced an I/O problem
    800      *      creating a new auth token, usually because of network trouble
    801      * </ul>
    802      * If the account is no longer present on the device, the return value is
    803      * authenticator-dependent.  The caller should verify the validity of the
    804      * account before requesting an auth token.
    805      */
    806     public AccountManagerFuture<Bundle> getAuthToken(
    807             final Account account, final String authTokenType, final Bundle options,
    808             final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
    809         if (account == null) throw new IllegalArgumentException("account is null");
    810         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    811         return new AmsTask(activity, handler, callback) {
    812             public void doWork() throws RemoteException {
    813                 mService.getAuthToken(mResponse, account, authTokenType,
    814                         false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
    815                         options);
    816             }
    817         }.start();
    818     }
    819 
    820     /**
    821      * Gets an auth token of the specified type for a particular account,
    822      * optionally raising a notification if the user must enter credentials.
    823      * This method is intended for background tasks and services where the
    824      * user should not be immediately interrupted with a password prompt.
    825      *
    826      * <p>If a previously generated auth token is cached for this account and
    827      * type, then it is returned.  Otherwise, if a saved password is
    828      * available, it is sent to the server to generate a new auth token.
    829      * Otherwise, an {@link Intent} is returned which, when started, will
    830      * prompt the user for a password.  If the notifyAuthFailure parameter is
    831      * set, a status bar notification is also created with the same Intent,
    832      * alerting the user that they need to enter a password at some point.
    833      *
    834      * <p>In that case, you may need to wait until the user responds, which
    835      * could take hours or days or forever.  When the user does respond and
    836      * supply a new password, the account manager will broadcast the
    837      * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
    838      * use to try again.
    839      *
    840      * <p>If notifyAuthFailure is not set, it is the application's
    841      * responsibility to launch the returned Intent at some point.
    842      * Either way, the result from this call will not wait for user action.
    843      *
    844      * <p>Some authenticators have auth token <em>types</em>, whose value
    845      * is authenticator-dependent.  Some services use different token types to
    846      * access different functionality -- for example, Google uses different auth
    847      * tokens to access Gmail and Google Calendar for the same account.
    848      *
    849      * <p>This method may be called from any thread, but the returned
    850      * {@link AccountManagerFuture} must not be used on the main thread.
    851      *
    852      * <p>This method requires the caller to hold the permission
    853      * {@link android.Manifest.permission#USE_CREDENTIALS}.
    854      *
    855      * @param account The account to fetch an auth token for
    856      * @param authTokenType The auth token type, an authenticator-dependent
    857      *     string token, must not be null
    858      * @param notifyAuthFailure True to add a notification to prompt the
    859      *     user for a password if necessary, false to leave that to the caller
    860      * @param callback Callback to invoke when the request completes,
    861      *     null for no callback
    862      * @param handler {@link Handler} identifying the callback thread,
    863      *     null for the main thread
    864      * @return An {@link AccountManagerFuture} which resolves to a Bundle with
    865      *     at least the following fields on success:
    866      * <ul>
    867      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
    868      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
    869      * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
    870      * </ul>
    871      *
    872      * (Other authenticator-specific values may be returned.)  If the user
    873      * must enter credentials, the returned Bundle contains only
    874      * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
    875      *
    876      * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
    877      * <ul>
    878      * <li> {@link AuthenticatorException} if the authenticator failed to respond
    879      * <li> {@link OperationCanceledException} if the operation is canceled for
    880      *      any reason, incluidng the user canceling a credential request
    881      * <li> {@link IOException} if the authenticator experienced an I/O problem
    882      *      creating a new auth token, usually because of network trouble
    883      * </ul>
    884      * If the account is no longer present on the device, the return value is
    885      * authenticator-dependent.  The caller should verify the validity of the
    886      * account before requesting an auth token.
    887      */
    888     public AccountManagerFuture<Bundle> getAuthToken(
    889             final Account account, final String authTokenType, final boolean notifyAuthFailure,
    890             AccountManagerCallback<Bundle> callback, Handler handler) {
    891         if (account == null) throw new IllegalArgumentException("account is null");
    892         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    893         return new AmsTask(null, handler, callback) {
    894             public void doWork() throws RemoteException {
    895                 mService.getAuthToken(mResponse, account, authTokenType,
    896                         notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
    897             }
    898         }.start();
    899     }
    900 
    901     /**
    902      * Asks the user to add an account of a specified type.  The authenticator
    903      * for this account type processes this request with the appropriate user
    904      * interface.  If the user does elect to create a new account, the account
    905      * name is returned.
    906      *
    907      * <p>This method may be called from any thread, but the returned
    908      * {@link AccountManagerFuture} must not be used on the main thread.
    909      *
    910      * <p>This method requires the caller to hold the permission
    911      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
    912      *
    913      * @param accountType The type of account to add; must not be null
    914      * @param authTokenType The type of auth token (see {@link #getAuthToken})
    915      *     this account will need to be able to generate, null for none
    916      * @param requiredFeatures The features (see {@link #hasFeatures}) this
    917      *     account must have, null for none
    918      * @param addAccountOptions Authenticator-specific options for the request,
    919      *     may be null or empty
    920      * @param activity The {@link Activity} context to use for launching a new
    921      *     authenticator-defined sub-Activity to prompt the user to create an
    922      *     account; used only to call startActivity(); if null, the prompt
    923      *     will not be launched directly, but the necessary {@link Intent}
    924      *     will be returned to the caller instead
    925      * @param callback Callback to invoke when the request completes,
    926      *     null for no callback
    927      * @param handler {@link Handler} identifying the callback thread,
    928      *     null for the main thread
    929      * @return An {@link AccountManagerFuture} which resolves to a Bundle with
    930      *     these fields if activity was specified and an account was created:
    931      * <ul>
    932      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
    933      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
    934      * </ul>
    935      *
    936      * If no activity was specified, the returned Bundle contains only
    937      * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
    938      * actual account creation process.  If an error occurred,
    939      * {@link AccountManagerFuture#getResult()} throws:
    940      * <ul>
    941      * <li> {@link AuthenticatorException} if no authenticator was registered for
    942      *      this account type or the authenticator failed to respond
    943      * <li> {@link OperationCanceledException} if the operation was canceled for
    944      *      any reason, including the user canceling the creation process
    945      * <li> {@link IOException} if the authenticator experienced an I/O problem
    946      *      creating a new account, usually because of network trouble
    947      * </ul>
    948      */
    949     public AccountManagerFuture<Bundle> addAccount(final String accountType,
    950             final String authTokenType, final String[] requiredFeatures,
    951             final Bundle addAccountOptions,
    952             final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
    953         if (accountType == null) throw new IllegalArgumentException("accountType is null");
    954         return new AmsTask(activity, handler, callback) {
    955             public void doWork() throws RemoteException {
    956                 mService.addAcount(mResponse, accountType, authTokenType,
    957                         requiredFeatures, activity != null, addAccountOptions);
    958             }
    959         }.start();
    960     }
    961 
    962     /**
    963      * Confirms that the user knows the password for an account to make extra
    964      * sure they are the owner of the account.  The user-entered password can
    965      * be supplied directly, otherwise the authenticator for this account type
    966      * prompts the user with the appropriate interface.  This method is
    967      * intended for applications which want extra assurance; for example, the
    968      * phone lock screen uses this to let the user unlock the phone with an
    969      * account password if they forget the lock pattern.
    970      *
    971      * <p>If the user-entered password matches a saved password for this
    972      * account, the request is considered valid; otherwise the authenticator
    973      * verifies the password (usually by contacting the server).
    974      *
    975      * <p>This method may be called from any thread, but the returned
    976      * {@link AccountManagerFuture} must not be used on the main thread.
    977      *
    978      * <p>This method requires the caller to hold the permission
    979      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
    980      *
    981      * @param account The account to confirm password knowledge for
    982      * @param options Authenticator-specific options for the request;
    983      *     if the {@link #KEY_PASSWORD} string field is present, the
    984      *     authenticator may use it directly rather than prompting the user;
    985      *     may be null or empty
    986      * @param activity The {@link Activity} context to use for launching a new
    987      *     authenticator-defined sub-Activity to prompt the user to enter a
    988      *     password; used only to call startActivity(); if null, the prompt
    989      *     will not be launched directly, but the necessary {@link Intent}
    990      *     will be returned to the caller instead
    991      * @param callback Callback to invoke when the request completes,
    992      *     null for no callback
    993      * @param handler {@link Handler} identifying the callback thread,
    994      *     null for the main thread
    995      * @return An {@link AccountManagerFuture} which resolves to a Bundle
    996      *     with these fields if activity or password was supplied and
    997      *     the account was successfully verified:
    998      * <ul>
    999      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
   1000      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
   1001      * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
   1002      * </ul>
   1003      *
   1004      * If no activity or password was specified, the returned Bundle contains
   1005      * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
   1006      * password prompt.  If an error occurred,
   1007      * {@link AccountManagerFuture#getResult()} throws:
   1008      * <ul>
   1009      * <li> {@link AuthenticatorException} if the authenticator failed to respond
   1010      * <li> {@link OperationCanceledException} if the operation was canceled for
   1011      *      any reason, including the user canceling the password prompt
   1012      * <li> {@link IOException} if the authenticator experienced an I/O problem
   1013      *      verifying the password, usually because of network trouble
   1014      * </ul>
   1015      */
   1016     public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
   1017             final Bundle options,
   1018             final Activity activity,
   1019             final AccountManagerCallback<Bundle> callback,
   1020             final Handler handler) {
   1021         if (account == null) throw new IllegalArgumentException("account is null");
   1022         return new AmsTask(activity, handler, callback) {
   1023             public void doWork() throws RemoteException {
   1024                 mService.confirmCredentials(mResponse, account, options, activity != null);
   1025             }
   1026         }.start();
   1027     }
   1028 
   1029     /**
   1030      * Asks the user to enter a new password for an account, updating the
   1031      * saved credentials for the account.  Normally this happens automatically
   1032      * when the server rejects credentials during an auth token fetch, but this
   1033      * can be invoked directly to ensure we have the correct credentials stored.
   1034      *
   1035      * <p>This method may be called from any thread, but the returned
   1036      * {@link AccountManagerFuture} must not be used on the main thread.
   1037      *
   1038      * <p>This method requires the caller to hold the permission
   1039      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
   1040      *
   1041      * @param account The account to update credentials for
   1042      * @param authTokenType The credentials entered must allow an auth token
   1043      *     of this type to be created (but no actual auth token is returned);
   1044      *     may be null
   1045      * @param options Authenticator-specific options for the request;
   1046      *     may be null or empty
   1047      * @param activity The {@link Activity} context to use for launching a new
   1048      *     authenticator-defined sub-Activity to prompt the user to enter a
   1049      *     password; used only to call startActivity(); if null, the prompt
   1050      *     will not be launched directly, but the necessary {@link Intent}
   1051      *     will be returned to the caller instead
   1052      * @param callback Callback to invoke when the request completes,
   1053      *     null for no callback
   1054      * @param handler {@link Handler} identifying the callback thread,
   1055      *     null for the main thread
   1056      * @return An {@link AccountManagerFuture} which resolves to a Bundle
   1057      *     with these fields if an activity was supplied and the account
   1058      *     credentials were successfully updated:
   1059      * <ul>
   1060      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
   1061      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
   1062      * </ul>
   1063      *
   1064      * If no activity was specified, the returned Bundle contains only
   1065      * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
   1066      * password prompt.  If an error occurred,
   1067      * {@link AccountManagerFuture#getResult()} throws:
   1068      * <ul>
   1069      * <li> {@link AuthenticatorException} if the authenticator failed to respond
   1070      * <li> {@link OperationCanceledException} if the operation was canceled for
   1071      *      any reason, including the user canceling the password prompt
   1072      * <li> {@link IOException} if the authenticator experienced an I/O problem
   1073      *      verifying the password, usually because of network trouble
   1074      * </ul>
   1075      */
   1076     public AccountManagerFuture<Bundle> updateCredentials(final Account account,
   1077             final String authTokenType,
   1078             final Bundle options, final Activity activity,
   1079             final AccountManagerCallback<Bundle> callback,
   1080             final Handler handler) {
   1081         if (account == null) throw new IllegalArgumentException("account is null");
   1082         return new AmsTask(activity, handler, callback) {
   1083             public void doWork() throws RemoteException {
   1084                 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
   1085                         options);
   1086             }
   1087         }.start();
   1088     }
   1089 
   1090     /**
   1091      * Offers the user an opportunity to change an authenticator's settings.
   1092      * These properties are for the authenticator in general, not a particular
   1093      * account.  Not all authenticators support this method.
   1094      *
   1095      * <p>This method may be called from any thread, but the returned
   1096      * {@link AccountManagerFuture} must not be used on the main thread.
   1097      *
   1098      * <p>This method requires the caller to hold the permission
   1099      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
   1100      *
   1101      * @param accountType The account type associated with the authenticator
   1102      *     to adjust
   1103      * @param activity The {@link Activity} context to use for launching a new
   1104      *     authenticator-defined sub-Activity to adjust authenticator settings;
   1105      *     used only to call startActivity(); if null, the settings dialog will
   1106      *     not be launched directly, but the necessary {@link Intent} will be
   1107      *     returned to the caller instead
   1108      * @param callback Callback to invoke when the request completes,
   1109      *     null for no callback
   1110      * @param handler {@link Handler} identifying the callback thread,
   1111      *     null for the main thread
   1112      * @return An {@link AccountManagerFuture} which resolves to a Bundle
   1113      *     which is empty if properties were edited successfully, or
   1114      *     if no activity was specified, contains only {@link #KEY_INTENT}
   1115      *     needed to launch the authenticator's settings dialog.
   1116      *     If an error occurred, {@link AccountManagerFuture#getResult()}
   1117      *     throws:
   1118      * <ul>
   1119      * <li> {@link AuthenticatorException} if no authenticator was registered for
   1120      *      this account type or the authenticator failed to respond
   1121      * <li> {@link OperationCanceledException} if the operation was canceled for
   1122      *      any reason, including the user canceling the settings dialog
   1123      * <li> {@link IOException} if the authenticator experienced an I/O problem
   1124      *      updating settings, usually because of network trouble
   1125      * </ul>
   1126      */
   1127     public AccountManagerFuture<Bundle> editProperties(final String accountType,
   1128             final Activity activity, final AccountManagerCallback<Bundle> callback,
   1129             final Handler handler) {
   1130         if (accountType == null) throw new IllegalArgumentException("accountType is null");
   1131         return new AmsTask(activity, handler, callback) {
   1132             public void doWork() throws RemoteException {
   1133                 mService.editProperties(mResponse, accountType, activity != null);
   1134             }
   1135         }.start();
   1136     }
   1137 
   1138     private void ensureNotOnMainThread() {
   1139         final Looper looper = Looper.myLooper();
   1140         if (looper != null && looper == mContext.getMainLooper()) {
   1141             final IllegalStateException exception = new IllegalStateException(
   1142                     "calling this from your main thread can lead to deadlock");
   1143             Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
   1144                     exception);
   1145             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
   1146                 throw exception;
   1147             }
   1148         }
   1149     }
   1150 
   1151     private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
   1152             final AccountManagerFuture<Bundle> future) {
   1153         handler = handler == null ? mMainHandler : handler;
   1154         handler.post(new Runnable() {
   1155             public void run() {
   1156                 callback.run(future);
   1157             }
   1158         });
   1159     }
   1160 
   1161     private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
   1162             final Account[] accounts) {
   1163         final Account[] accountsCopy = new Account[accounts.length];
   1164         // send a copy to make sure that one doesn't
   1165         // change what another sees
   1166         System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
   1167         handler = (handler == null) ? mMainHandler : handler;
   1168         handler.post(new Runnable() {
   1169             public void run() {
   1170                 try {
   1171                     listener.onAccountsUpdated(accountsCopy);
   1172                 } catch (SQLException e) {
   1173                     // Better luck next time.  If the problem was disk-full,
   1174                     // the STORAGE_OK intent will re-trigger the update.
   1175                     Log.e(TAG, "Can't update accounts", e);
   1176                 }
   1177             }
   1178         });
   1179     }
   1180 
   1181     private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
   1182         final IAccountManagerResponse mResponse;
   1183         final Handler mHandler;
   1184         final AccountManagerCallback<Bundle> mCallback;
   1185         final Activity mActivity;
   1186         public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
   1187             super(new Callable<Bundle>() {
   1188                 public Bundle call() throws Exception {
   1189                     throw new IllegalStateException("this should never be called");
   1190                 }
   1191             });
   1192 
   1193             mHandler = handler;
   1194             mCallback = callback;
   1195             mActivity = activity;
   1196             mResponse = new Response();
   1197         }
   1198 
   1199         public final AccountManagerFuture<Bundle> start() {
   1200             try {
   1201                 doWork();
   1202             } catch (RemoteException e) {
   1203                 setException(e);
   1204             }
   1205             return this;
   1206         }
   1207 
   1208         protected void set(Bundle bundle) {
   1209             // TODO: somehow a null is being set as the result of the Future. Log this
   1210             // case to help debug where this is occurring. When this bug is fixed this
   1211             // condition statement should be removed.
   1212             if (bundle == null) {
   1213                 Log.e(TAG, "the bundle must not be null", new Exception());
   1214             }
   1215             super.set(bundle);
   1216         }
   1217 
   1218         public abstract void doWork() throws RemoteException;
   1219 
   1220         private Bundle internalGetResult(Long timeout, TimeUnit unit)
   1221                 throws OperationCanceledException, IOException, AuthenticatorException {
   1222             if (!isDone()) {
   1223                 ensureNotOnMainThread();
   1224             }
   1225             try {
   1226                 if (timeout == null) {
   1227                     return get();
   1228                 } else {
   1229                     return get(timeout, unit);
   1230                 }
   1231             } catch (CancellationException e) {
   1232                 throw new OperationCanceledException();
   1233             } catch (TimeoutException e) {
   1234                 // fall through and cancel
   1235             } catch (InterruptedException e) {
   1236                 // fall through and cancel
   1237             } catch (ExecutionException e) {
   1238                 final Throwable cause = e.getCause();
   1239                 if (cause instanceof IOException) {
   1240                     throw (IOException) cause;
   1241                 } else if (cause instanceof UnsupportedOperationException) {
   1242                     throw new AuthenticatorException(cause);
   1243                 } else if (cause instanceof AuthenticatorException) {
   1244                     throw (AuthenticatorException) cause;
   1245                 } else if (cause instanceof RuntimeException) {
   1246                     throw (RuntimeException) cause;
   1247                 } else if (cause instanceof Error) {
   1248                     throw (Error) cause;
   1249                 } else {
   1250                     throw new IllegalStateException(cause);
   1251                 }
   1252             } finally {
   1253                 cancel(true /* interrupt if running */);
   1254             }
   1255             throw new OperationCanceledException();
   1256         }
   1257 
   1258         public Bundle getResult()
   1259                 throws OperationCanceledException, IOException, AuthenticatorException {
   1260             return internalGetResult(null, null);
   1261         }
   1262 
   1263         public Bundle getResult(long timeout, TimeUnit unit)
   1264                 throws OperationCanceledException, IOException, AuthenticatorException {
   1265             return internalGetResult(timeout, unit);
   1266         }
   1267 
   1268         protected void done() {
   1269             if (mCallback != null) {
   1270                 postToHandler(mHandler, mCallback, this);
   1271             }
   1272         }
   1273 
   1274         /** Handles the responses from the AccountManager */
   1275         private class Response extends IAccountManagerResponse.Stub {
   1276             public void onResult(Bundle bundle) {
   1277                 Intent intent = bundle.getParcelable("intent");
   1278                 if (intent != null && mActivity != null) {
   1279                     // since the user provided an Activity we will silently start intents
   1280                     // that we see
   1281                     mActivity.startActivity(intent);
   1282                     // leave the Future running to wait for the real response to this request
   1283                 } else if (bundle.getBoolean("retry")) {
   1284                     try {
   1285                         doWork();
   1286                     } catch (RemoteException e) {
   1287                         // this will only happen if the system process is dead, which means
   1288                         // we will be dying ourselves
   1289                     }
   1290                 } else {
   1291                     set(bundle);
   1292                 }
   1293             }
   1294 
   1295             public void onError(int code, String message) {
   1296                 if (code == ERROR_CODE_CANCELED) {
   1297                     // the authenticator indicated that this request was canceled, do so now
   1298                     cancel(true /* mayInterruptIfRunning */);
   1299                     return;
   1300                 }
   1301                 setException(convertErrorToException(code, message));
   1302             }
   1303         }
   1304 
   1305     }
   1306 
   1307     private abstract class BaseFutureTask<T> extends FutureTask<T> {
   1308         final public IAccountManagerResponse mResponse;
   1309         final Handler mHandler;
   1310 
   1311         public BaseFutureTask(Handler handler) {
   1312             super(new Callable<T>() {
   1313                 public T call() throws Exception {
   1314                     throw new IllegalStateException("this should never be called");
   1315                 }
   1316             });
   1317             mHandler = handler;
   1318             mResponse = new Response();
   1319         }
   1320 
   1321         public abstract void doWork() throws RemoteException;
   1322 
   1323         public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
   1324 
   1325         protected void postRunnableToHandler(Runnable runnable) {
   1326             Handler handler = (mHandler == null) ? mMainHandler : mHandler;
   1327             handler.post(runnable);
   1328         }
   1329 
   1330         protected void startTask() {
   1331             try {
   1332                 doWork();
   1333             } catch (RemoteException e) {
   1334                 setException(e);
   1335             }
   1336         }
   1337 
   1338         protected class Response extends IAccountManagerResponse.Stub {
   1339             public void onResult(Bundle bundle) {
   1340                 try {
   1341                     T result = bundleToResult(bundle);
   1342                     if (result == null) {
   1343                         return;
   1344                     }
   1345                     set(result);
   1346                     return;
   1347                 } catch (ClassCastException e) {
   1348                     // we will set the exception below
   1349                 } catch (AuthenticatorException e) {
   1350                     // we will set the exception below
   1351                 }
   1352                 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
   1353             }
   1354 
   1355             public void onError(int code, String message) {
   1356                 if (code == ERROR_CODE_CANCELED) {
   1357                     cancel(true /* mayInterruptIfRunning */);
   1358                     return;
   1359                 }
   1360                 setException(convertErrorToException(code, message));
   1361             }
   1362         }
   1363     }
   1364 
   1365     private abstract class Future2Task<T>
   1366             extends BaseFutureTask<T> implements AccountManagerFuture<T> {
   1367         final AccountManagerCallback<T> mCallback;
   1368         public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
   1369             super(handler);
   1370             mCallback = callback;
   1371         }
   1372 
   1373         protected void done() {
   1374             if (mCallback != null) {
   1375                 postRunnableToHandler(new Runnable() {
   1376                     public void run() {
   1377                         mCallback.run(Future2Task.this);
   1378                     }
   1379                 });
   1380             }
   1381         }
   1382 
   1383         public Future2Task<T> start() {
   1384             startTask();
   1385             return this;
   1386         }
   1387 
   1388         private T internalGetResult(Long timeout, TimeUnit unit)
   1389                 throws OperationCanceledException, IOException, AuthenticatorException {
   1390             if (!isDone()) {
   1391                 ensureNotOnMainThread();
   1392             }
   1393             try {
   1394                 if (timeout == null) {
   1395                     return get();
   1396                 } else {
   1397                     return get(timeout, unit);
   1398                 }
   1399             } catch (InterruptedException e) {
   1400                 // fall through and cancel
   1401             } catch (TimeoutException e) {
   1402                 // fall through and cancel
   1403             } catch (CancellationException e) {
   1404                 // fall through and cancel
   1405             } catch (ExecutionException e) {
   1406                 final Throwable cause = e.getCause();
   1407                 if (cause instanceof IOException) {
   1408                     throw (IOException) cause;
   1409                 } else if (cause instanceof UnsupportedOperationException) {
   1410                     throw new AuthenticatorException(cause);
   1411                 } else if (cause instanceof AuthenticatorException) {
   1412                     throw (AuthenticatorException) cause;
   1413                 } else if (cause instanceof RuntimeException) {
   1414                     throw (RuntimeException) cause;
   1415                 } else if (cause instanceof Error) {
   1416                     throw (Error) cause;
   1417                 } else {
   1418                     throw new IllegalStateException(cause);
   1419                 }
   1420             } finally {
   1421                 cancel(true /* interrupt if running */);
   1422             }
   1423             throw new OperationCanceledException();
   1424         }
   1425 
   1426         public T getResult()
   1427                 throws OperationCanceledException, IOException, AuthenticatorException {
   1428             return internalGetResult(null, null);
   1429         }
   1430 
   1431         public T getResult(long timeout, TimeUnit unit)
   1432                 throws OperationCanceledException, IOException, AuthenticatorException {
   1433             return internalGetResult(timeout, unit);
   1434         }
   1435 
   1436     }
   1437 
   1438     private Exception convertErrorToException(int code, String message) {
   1439         if (code == ERROR_CODE_NETWORK_ERROR) {
   1440             return new IOException(message);
   1441         }
   1442 
   1443         if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
   1444             return new UnsupportedOperationException(message);
   1445         }
   1446 
   1447         if (code == ERROR_CODE_INVALID_RESPONSE) {
   1448             return new AuthenticatorException(message);
   1449         }
   1450 
   1451         if (code == ERROR_CODE_BAD_ARGUMENTS) {
   1452             return new IllegalArgumentException(message);
   1453         }
   1454 
   1455         return new AuthenticatorException(message);
   1456     }
   1457 
   1458     private class GetAuthTokenByTypeAndFeaturesTask
   1459             extends AmsTask implements AccountManagerCallback<Bundle> {
   1460         GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
   1461                 final String[] features, Activity activityForPrompting,
   1462                 final Bundle addAccountOptions, final Bundle loginOptions,
   1463                 AccountManagerCallback<Bundle> callback, Handler handler) {
   1464             super(activityForPrompting, handler, callback);
   1465             if (accountType == null) throw new IllegalArgumentException("account type is null");
   1466             mAccountType = accountType;
   1467             mAuthTokenType = authTokenType;
   1468             mFeatures = features;
   1469             mAddAccountOptions = addAccountOptions;
   1470             mLoginOptions = loginOptions;
   1471             mMyCallback = this;
   1472         }
   1473         volatile AccountManagerFuture<Bundle> mFuture = null;
   1474         final String mAccountType;
   1475         final String mAuthTokenType;
   1476         final String[] mFeatures;
   1477         final Bundle mAddAccountOptions;
   1478         final Bundle mLoginOptions;
   1479         final AccountManagerCallback<Bundle> mMyCallback;
   1480         private volatile int mNumAccounts = 0;
   1481 
   1482         public void doWork() throws RemoteException {
   1483             getAccountsByTypeAndFeatures(mAccountType, mFeatures,
   1484                     new AccountManagerCallback<Account[]>() {
   1485                         public void run(AccountManagerFuture<Account[]> future) {
   1486                             Account[] accounts;
   1487                             try {
   1488                                 accounts = future.getResult();
   1489                             } catch (OperationCanceledException e) {
   1490                                 setException(e);
   1491                                 return;
   1492                             } catch (IOException e) {
   1493                                 setException(e);
   1494                                 return;
   1495                             } catch (AuthenticatorException e) {
   1496                                 setException(e);
   1497                                 return;
   1498                             }
   1499 
   1500                             mNumAccounts = accounts.length;
   1501 
   1502                             if (accounts.length == 0) {
   1503                                 if (mActivity != null) {
   1504                                     // no accounts, add one now. pretend that the user directly
   1505                                     // made this request
   1506                                     mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
   1507                                             mAddAccountOptions, mActivity, mMyCallback, mHandler);
   1508                                 } else {
   1509                                     // send result since we can't prompt to add an account
   1510                                     Bundle result = new Bundle();
   1511                                     result.putString(KEY_ACCOUNT_NAME, null);
   1512                                     result.putString(KEY_ACCOUNT_TYPE, null);
   1513                                     result.putString(KEY_AUTHTOKEN, null);
   1514                                     try {
   1515                                         mResponse.onResult(result);
   1516                                     } catch (RemoteException e) {
   1517                                         // this will never happen
   1518                                     }
   1519                                     // we are done
   1520                                 }
   1521                             } else if (accounts.length == 1) {
   1522                                 // have a single account, return an authtoken for it
   1523                                 if (mActivity == null) {
   1524                                     mFuture = getAuthToken(accounts[0], mAuthTokenType,
   1525                                             false /* notifyAuthFailure */, mMyCallback, mHandler);
   1526                                 } else {
   1527                                     mFuture = getAuthToken(accounts[0],
   1528                                             mAuthTokenType, mLoginOptions,
   1529                                             mActivity, mMyCallback, mHandler);
   1530                                 }
   1531                             } else {
   1532                                 if (mActivity != null) {
   1533                                     IAccountManagerResponse chooseResponse =
   1534                                             new IAccountManagerResponse.Stub() {
   1535                                         public void onResult(Bundle value) throws RemoteException {
   1536                                             Account account = new Account(
   1537                                                     value.getString(KEY_ACCOUNT_NAME),
   1538                                                     value.getString(KEY_ACCOUNT_TYPE));
   1539                                             mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
   1540                                                     mActivity, mMyCallback, mHandler);
   1541                                         }
   1542 
   1543                                         public void onError(int errorCode, String errorMessage)
   1544                                                 throws RemoteException {
   1545                                             mResponse.onError(errorCode, errorMessage);
   1546                                         }
   1547                                     };
   1548                                     // have many accounts, launch the chooser
   1549                                     Intent intent = new Intent();
   1550                                     intent.setClassName("android",
   1551                                             "android.accounts.ChooseAccountActivity");
   1552                                     intent.putExtra(KEY_ACCOUNTS, accounts);
   1553                                     intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
   1554                                             new AccountManagerResponse(chooseResponse));
   1555                                     mActivity.startActivity(intent);
   1556                                     // the result will arrive via the IAccountManagerResponse
   1557                                 } else {
   1558                                     // send result since we can't prompt to select an account
   1559                                     Bundle result = new Bundle();
   1560                                     result.putString(KEY_ACCOUNTS, null);
   1561                                     try {
   1562                                         mResponse.onResult(result);
   1563                                     } catch (RemoteException e) {
   1564                                         // this will never happen
   1565                                     }
   1566                                     // we are done
   1567                                 }
   1568                             }
   1569                         }}, mHandler);
   1570         }
   1571 
   1572         public void run(AccountManagerFuture<Bundle> future) {
   1573             try {
   1574                 final Bundle result = future.getResult();
   1575                 if (mNumAccounts == 0) {
   1576                     final String accountName = result.getString(KEY_ACCOUNT_NAME);
   1577                     final String accountType = result.getString(KEY_ACCOUNT_TYPE);
   1578                     if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
   1579                         setException(new AuthenticatorException("account not in result"));
   1580                         return;
   1581                     }
   1582                     final Account account = new Account(accountName, accountType);
   1583                     mNumAccounts = 1;
   1584                     getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
   1585                             mMyCallback, mHandler);
   1586                     return;
   1587                 }
   1588                 set(result);
   1589             } catch (OperationCanceledException e) {
   1590                 cancel(true /* mayInterruptIfRUnning */);
   1591             } catch (IOException e) {
   1592                 setException(e);
   1593             } catch (AuthenticatorException e) {
   1594                 setException(e);
   1595             }
   1596         }
   1597     }
   1598 
   1599     /**
   1600      * This convenience helper combines the functionality of
   1601      * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and
   1602      * {@link #addAccount}.
   1603      *
   1604      * <p>This method gets a list of the accounts matching the
   1605      * specified type and feature set; if there is exactly one, it is
   1606      * used; if there are more than one, the user is prompted to pick one;
   1607      * if there are none, the user is prompted to add one.  Finally,
   1608      * an auth token is acquired for the chosen account.
   1609      *
   1610      * <p>This method may be called from any thread, but the returned
   1611      * {@link AccountManagerFuture} must not be used on the main thread.
   1612      *
   1613      * <p>This method requires the caller to hold the permission
   1614      * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
   1615      *
   1616      * @param accountType The account type required
   1617      *     (see {@link #getAccountsByType}), must not be null
   1618      * @param authTokenType The desired auth token type
   1619      *     (see {@link #getAuthToken}), must not be null
   1620      * @param features Required features for the account
   1621      *     (see {@link #getAccountsByTypeAndFeatures}), may be null or empty
   1622      * @param activity The {@link Activity} context to use for launching new
   1623      *     sub-Activities to prompt to add an account, select an account,
   1624      *     and/or enter a password, as necessary; used only to call
   1625      *     startActivity(); should not be null
   1626      * @param addAccountOptions Authenticator-specific options to use for
   1627      *     adding new accounts; may be null or empty
   1628      * @param getAuthTokenOptions Authenticator-specific options to use for
   1629      *     getting auth tokens; may be null or empty
   1630      * @param callback Callback to invoke when the request completes,
   1631      *     null for no callback
   1632      * @param handler {@link Handler} identifying the callback thread,
   1633      *     null for the main thread
   1634      * @return An {@link AccountManagerFuture} which resolves to a Bundle with
   1635      *     at least the following fields:
   1636      * <ul>
   1637      * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
   1638      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
   1639      * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
   1640      * </ul>
   1641      *
   1642      * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
   1643      * <ul>
   1644      * <li> {@link AuthenticatorException} if no authenticator was registered for
   1645      *      this account type or the authenticator failed to respond
   1646      * <li> {@link OperationCanceledException} if the operation was canceled for
   1647      *      any reason, including the user canceling any operation
   1648      * <li> {@link IOException} if the authenticator experienced an I/O problem
   1649      *      updating settings, usually because of network trouble
   1650      * </ul>
   1651      */
   1652     public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
   1653             final String accountType, final String authTokenType, final String[] features,
   1654             final Activity activity, final Bundle addAccountOptions,
   1655             final Bundle getAuthTokenOptions,
   1656             final AccountManagerCallback<Bundle> callback, final Handler handler) {
   1657         if (accountType == null) throw new IllegalArgumentException("account type is null");
   1658         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
   1659         final GetAuthTokenByTypeAndFeaturesTask task =
   1660                 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
   1661                 activity, addAccountOptions, getAuthTokenOptions, callback, handler);
   1662         task.start();
   1663         return task;
   1664     }
   1665 
   1666     private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
   1667             Maps.newHashMap();
   1668 
   1669     /**
   1670      * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
   1671      * so that it can read the updated list of accounts and send them to the listener
   1672      * in mAccountsUpdatedListeners.
   1673      */
   1674     private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
   1675         public void onReceive(final Context context, final Intent intent) {
   1676             final Account[] accounts = getAccounts();
   1677             // send the result to the listeners
   1678             synchronized (mAccountsUpdatedListeners) {
   1679                 for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
   1680                         mAccountsUpdatedListeners.entrySet()) {
   1681                     postToHandler(entry.getValue(), entry.getKey(), accounts);
   1682                 }
   1683             }
   1684         }
   1685     };
   1686 
   1687     /**
   1688      * Adds an {@link OnAccountsUpdateListener} to this instance of the
   1689      * {@link AccountManager}.  This listener will be notified whenever the
   1690      * list of accounts on the device changes.
   1691      *
   1692      * <p>As long as this listener is present, the AccountManager instance
   1693      * will not be garbage-collected, and neither will the {@link Context}
   1694      * used to retrieve it, which may be a large Activity instance.  To avoid
   1695      * memory leaks, you must remove this listener before then.  Normally
   1696      * listeners are added in an Activity or Service's {@link Activity#onCreate}
   1697      * and removed in {@link Activity#onDestroy}.
   1698      *
   1699      * <p>It is safe to call this method from the main thread.
   1700      *
   1701      * <p>No permission is required to call this method.
   1702      *
   1703      * @param listener The listener to send notifications to
   1704      * @param handler {@link Handler} identifying the thread to use
   1705      *     for notifications, null for the main thread
   1706      * @param updateImmediately If true, the listener will be invoked
   1707      *     (on the handler thread) right away with the current account list
   1708      * @throws IllegalArgumentException if listener is null
   1709      * @throws IllegalStateException if listener was already added
   1710      */
   1711     public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
   1712             Handler handler, boolean updateImmediately) {
   1713         if (listener == null) {
   1714             throw new IllegalArgumentException("the listener is null");
   1715         }
   1716         synchronized (mAccountsUpdatedListeners) {
   1717             if (mAccountsUpdatedListeners.containsKey(listener)) {
   1718                 throw new IllegalStateException("this listener is already added");
   1719             }
   1720             final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
   1721 
   1722             mAccountsUpdatedListeners.put(listener, handler);
   1723 
   1724             if (wasEmpty) {
   1725                 // Register a broadcast receiver to monitor account changes
   1726                 IntentFilter intentFilter = new IntentFilter();
   1727                 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
   1728                 // To recover from disk-full.
   1729                 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
   1730                 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
   1731             }
   1732         }
   1733 
   1734         if (updateImmediately) {
   1735             postToHandler(handler, listener, getAccounts());
   1736         }
   1737     }
   1738 
   1739     /**
   1740      * Removes an {@link OnAccountsUpdateListener} previously registered with
   1741      * {@link #addOnAccountsUpdatedListener}.  The listener will no longer
   1742      * receive notifications of account changes.
   1743      *
   1744      * <p>It is safe to call this method from the main thread.
   1745      *
   1746      * <p>No permission is required to call this method.
   1747      *
   1748      * @param listener The previously added listener to remove
   1749      * @throws IllegalArgumentException if listener is null
   1750      * @throws IllegalStateException if listener was not already added
   1751      */
   1752     public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
   1753         if (listener == null) throw new IllegalArgumentException("listener is null");
   1754         synchronized (mAccountsUpdatedListeners) {
   1755             if (!mAccountsUpdatedListeners.containsKey(listener)) {
   1756                 Log.e(TAG, "Listener was not previously added");
   1757                 return;
   1758             }
   1759             mAccountsUpdatedListeners.remove(listener);
   1760             if (mAccountsUpdatedListeners.isEmpty()) {
   1761                 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
   1762             }
   1763         }
   1764     }
   1765 }
   1766