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