blob: be15ac9968346f9652eceee627a480a171573e10 [file] [log] [blame]
Fred Quintana60307342009-03-24 22:48:12 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accounts;
18
Fred Quintana60307342009-03-24 22:48:12 -070019import android.app.Activity;
20import android.content.Intent;
21import android.content.Context;
Fred Quintanad9d2f112009-04-23 13:36:27 -070022import android.content.IntentFilter;
23import android.content.BroadcastReceiver;
Costin Manolacheb6437242009-09-10 16:14:12 -070024import android.database.SQLException;
Fred Quintanaa698f422009-04-08 19:14:54 -070025import android.os.Bundle;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.RemoteException;
Fred Quintana33269202009-04-20 16:05:10 -070029import android.os.Parcelable;
Fred Quintana751fdc02010-02-09 14:13:18 -080030import android.os.Build;
Costin Manolacheb6437242009-09-10 16:14:12 -070031import android.util.Log;
Fred Quintana60307342009-03-24 22:48:12 -070032
Fred Quintanaa698f422009-04-08 19:14:54 -070033import java.io.IOException;
34import java.util.concurrent.Callable;
35import java.util.concurrent.CancellationException;
36import java.util.concurrent.ExecutionException;
37import java.util.concurrent.FutureTask;
38import java.util.concurrent.TimeoutException;
39import java.util.concurrent.TimeUnit;
Fred Quintanad9d2f112009-04-23 13:36:27 -070040import java.util.HashMap;
41import java.util.Map;
42
43import com.google.android.collect.Maps;
Fred Quintana60307342009-03-24 22:48:12 -070044
45/**
Fred Quintana756b7352009-10-21 13:43:10 -070046 * A class that helps with interactions with the AccountManager Service. It provides
Fred Quintana60307342009-03-24 22:48:12 -070047 * methods to allow for account, password, and authtoken management for all accounts on the
Fred Quintana4db3a5b2009-10-05 17:19:03 -070048 * device. One accesses the {@link AccountManager} by calling:
Fred Quintana756b7352009-10-21 13:43:10 -070049 * <pre>
Fred Quintanaa698f422009-04-08 19:14:54 -070050 * AccountManager accountManager = AccountManager.get(context);
Fred Quintana756b7352009-10-21 13:43:10 -070051 * </pre>
Fred Quintana60307342009-03-24 22:48:12 -070052 *
53 * <p>
Fred Quintana756b7352009-10-21 13:43:10 -070054 * The AccountManager Service provides storage for the accounts known to the system,
55 * provides methods to manage them, and allows the registration of authenticators to
56 * which operations such as addAccount and getAuthToken are delegated.
57 * <p>
58 * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters.
59 * These calls return immediately but run asynchronously. If a callback is provided then
60 * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully
61 * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the
62 * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which
63 * either returns the result or throws an exception as appropriate.
64 * <p>
65 * The asynchronous request can be made blocking by not providing a callback and instead
66 * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will
67 * cause the running thread to block until the result is returned. Keep in mind that one
68 * should not block the main thread in this way. Instead one should either use a callback,
69 * thus making the call asynchronous, or make the blocking call on a separate thread.
70 * <p>
71 * If one wants to ensure that the callback is invoked from a specific handler then they should
72 * pass the handler to the request. This makes it easier to ensure thread-safety by running
73 * all of one's logic from a single handler.
Fred Quintana60307342009-03-24 22:48:12 -070074 */
75public class AccountManager {
76 private static final String TAG = "AccountManager";
77
Fred Quintanaf7ae77c2009-10-02 17:19:31 -070078 public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
79 public static final int ERROR_CODE_NETWORK_ERROR = 3;
80 public static final int ERROR_CODE_CANCELED = 4;
81 public static final int ERROR_CODE_INVALID_RESPONSE = 5;
82 public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
83 public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
84 public static final int ERROR_CODE_BAD_REQUEST = 8;
Fred Quintana756b7352009-10-21 13:43:10 -070085
Fred Quintanaf7ae77c2009-10-02 17:19:31 -070086 public static final String KEY_ACCOUNTS = "accounts";
87 public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
88 public static final String KEY_USERDATA = "userdata";
89 public static final String KEY_AUTHTOKEN = "authtoken";
90 public static final String KEY_PASSWORD = "password";
91 public static final String KEY_ACCOUNT_NAME = "authAccount";
92 public static final String KEY_ACCOUNT_TYPE = "accountType";
93 public static final String KEY_ERROR_CODE = "errorCode";
94 public static final String KEY_ERROR_MESSAGE = "errorMessage";
95 public static final String KEY_INTENT = "intent";
96 public static final String KEY_BOOLEAN_RESULT = "booleanResult";
97 public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
98 public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
99 public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
100 public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
101 public static final String ACTION_AUTHENTICATOR_INTENT =
102 "android.accounts.AccountAuthenticator";
103 public static final String AUTHENTICATOR_META_DATA_NAME =
104 "android.accounts.AccountAuthenticator";
105 public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
106
Fred Quintana60307342009-03-24 22:48:12 -0700107 private final Context mContext;
108 private final IAccountManager mService;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700109 private final Handler mMainHandler;
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700110 /**
111 * Action sent as a broadcast Intent by the AccountsService
112 * when accounts are added to and/or removed from the device's
113 * database.
114 */
115 public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
116 "android.accounts.LOGIN_ACCOUNTS_CHANGED";
Fred Quintana60307342009-03-24 22:48:12 -0700117
Fred Quintana33269202009-04-20 16:05:10 -0700118 /**
119 * @hide
120 */
Fred Quintana60307342009-03-24 22:48:12 -0700121 public AccountManager(Context context, IAccountManager service) {
122 mContext = context;
123 mService = service;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700124 mMainHandler = new Handler(mContext.getMainLooper());
Fred Quintana60307342009-03-24 22:48:12 -0700125 }
126
Fred Quintana0eabf022009-04-27 15:08:17 -0700127 /**
128 * @hide used for testing only
129 */
130 public AccountManager(Context context, IAccountManager service, Handler handler) {
131 mContext = context;
132 mService = service;
133 mMainHandler = handler;
134 }
135
Fred Quintana756b7352009-10-21 13:43:10 -0700136 /**
137 * Retrieve an AccountManager instance that is associated with the context that is passed in.
138 * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally,
139 * so the caller must take care to use a {@link Context} whose lifetime is associated with
140 * the listener registration.
141 * @param context The {@link Context} to use when necessary
142 * @return an {@link AccountManager} instance that is associated with context
143 */
Fred Quintanaa698f422009-04-08 19:14:54 -0700144 public static AccountManager get(Context context) {
145 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
146 }
147
Fred Quintana756b7352009-10-21 13:43:10 -0700148 /**
149 * Get the password that is associated with the account. Returns null if the account does
150 * not exist.
151 * <p>
152 * Requires that the caller has permission
153 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
154 * with the same UID as the Authenticator for the account.
155 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700156 public String getPassword(final Account account) {
Fred Quintana60307342009-03-24 22:48:12 -0700157 try {
158 return mService.getPassword(account);
159 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700160 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -0700161 throw new RuntimeException(e);
162 }
163 }
164
Fred Quintana756b7352009-10-21 13:43:10 -0700165 /**
166 * Get the user data named by "key" that is associated with the account.
167 * Returns null if the account does not exist or if it does not have a value for key.
168 * <p>
169 * Requires that the caller has permission
170 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
171 * with the same UID as the Authenticator for the account.
172 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700173 public String getUserData(final Account account, final String key) {
Fred Quintana60307342009-03-24 22:48:12 -0700174 try {
175 return mService.getUserData(account, key);
176 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700177 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -0700178 throw new RuntimeException(e);
179 }
180 }
181
Fred Quintana756b7352009-10-21 13:43:10 -0700182 /**
183 * Query the AccountManager Service for an array that contains a
184 * {@link AuthenticatorDescription} for each registered authenticator.
185 * @return an array that contains all the authenticators known to the AccountManager service.
186 * This array will be empty if there are no authenticators and will never return null.
187 * <p>
188 * No permission is required to make this call.
189 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700190 public AuthenticatorDescription[] getAuthenticatorTypes() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700191 try {
192 return mService.getAuthenticatorTypes();
193 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700194 // will never happen
Fred Quintanaa698f422009-04-08 19:14:54 -0700195 throw new RuntimeException(e);
196 }
197 }
198
Fred Quintana756b7352009-10-21 13:43:10 -0700199 /**
200 * Query the AccountManager Service for all accounts.
201 * @return an array that contains all the accounts known to the AccountManager service.
202 * This array will be empty if there are no accounts and will never return null.
203 * <p>
204 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
205 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700206 public Account[] getAccounts() {
Fred Quintana60307342009-03-24 22:48:12 -0700207 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700208 return mService.getAccounts(null);
Fred Quintana60307342009-03-24 22:48:12 -0700209 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700210 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700211 throw new RuntimeException(e);
212 }
213 }
214
Fred Quintana756b7352009-10-21 13:43:10 -0700215 /**
216 * Query the AccountManager for the set of accounts that have a given type. If null
217 * is passed as the type than all accounts are returned.
218 * @param type the account type by which to filter, or null to get all accounts
219 * @return an array that contains the accounts that match the specified type. This array
220 * will be empty if no accounts match. It will never return null.
221 * <p>
222 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
223 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700224 public Account[] getAccountsByType(String type) {
Fred Quintana60307342009-03-24 22:48:12 -0700225 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700226 return mService.getAccounts(type);
Fred Quintana60307342009-03-24 22:48:12 -0700227 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700228 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700229 throw new RuntimeException(e);
230 }
231 }
232
Fred Quintana756b7352009-10-21 13:43:10 -0700233 /**
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800234 * Tests that the given account has the specified features. If this account does not exist
235 * then this call returns false.
236 * <p>
237 * This call returns immediately but runs asynchronously and the result is accessed via the
238 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
239 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
240 * method asynchronously then they will generally pass in a callback object that will get
241 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
242 * they will generally pass null for the callback and instead call
243 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
244 * which will then block until the request completes.
245 * <p>
246 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
247 *
248 * @param account The {@link Account} to test
249 * @param features the features for which to test
250 * @param callback A callback to invoke when the request completes. If null then
251 * no callback is invoked.
252 * @param handler The {@link Handler} to use to invoke the callback. If null then the
253 * main thread's {@link Handler} is used.
254 * @return an {@link AccountManagerFuture} that represents the future result of the call.
255 * The future result is a {@link Boolean} that is true if the account exists and has the
256 * specified features.
257 */
Fred Quintana3084a6f2010-01-14 18:02:03 -0800258 public AccountManagerFuture<Boolean> hasFeatures(final Account account,
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800259 final String[] features,
260 AccountManagerCallback<Boolean> callback, Handler handler) {
261 return new Future2Task<Boolean>(handler, callback) {
262 public void doWork() throws RemoteException {
Fred Quintana3084a6f2010-01-14 18:02:03 -0800263 mService.hasFeatures(mResponse, account, features);
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800264 }
265 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
266 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
267 throw new AuthenticatorException("no result in response");
268 }
269 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
270 }
271 }.start();
272 }
273
274 /**
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800275 * Add an account to the AccountManager's set of known accounts.
Fred Quintana756b7352009-10-21 13:43:10 -0700276 * <p>
277 * Requires that the caller has permission
278 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
279 * with the same UID as the Authenticator for the account.
280 * @param account The account to add
281 * @param password The password to associate with the account. May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700282 * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null.
Fred Quintana756b7352009-10-21 13:43:10 -0700283 * @return true if the account was sucessfully added, false otherwise, for example,
284 * if the account already exists or if the account is null
285 */
Fred Quintana31957f12009-10-21 13:43:10 -0700286 public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
Fred Quintana60307342009-03-24 22:48:12 -0700287 try {
Fred Quintana31957f12009-10-21 13:43:10 -0700288 return mService.addAccount(account, password, userdata);
Fred Quintana60307342009-03-24 22:48:12 -0700289 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700290 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700291 throw new RuntimeException(e);
292 }
293 }
294
Fred Quintana756b7352009-10-21 13:43:10 -0700295 /**
296 * Removes the given account. If this account does not exist then this call has no effect.
297 * <p>
298 * This call returns immediately but runs asynchronously and the result is accessed via the
299 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
300 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
301 * method asynchronously then they will generally pass in a callback object that will get
302 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
303 * they will generally pass null for the callback and instead call
304 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
305 * which will then block until the request completes.
306 * <p>
307 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
308 *
309 * @param account The {@link Account} to remove
310 * @param callback A callback to invoke when the request completes. If null then
311 * no callback is invoked.
312 * @param handler The {@link Handler} to use to invoke the callback. If null then the
313 * main thread's {@link Handler} is used.
314 * @return an {@link AccountManagerFuture} that represents the future result of the call.
315 * The future result is a {@link Boolean} that is true if the account is successfully removed
316 * or false if the authenticator refuses to remove the account.
317 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700318 public AccountManagerFuture<Boolean> removeAccount(final Account account,
319 AccountManagerCallback<Boolean> callback, Handler handler) {
320 return new Future2Task<Boolean>(handler, callback) {
321 public void doWork() throws RemoteException {
322 mService.removeAccount(mResponse, account);
Fred Quintanaa698f422009-04-08 19:14:54 -0700323 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700324 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700325 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700326 throw new AuthenticatorException("no result in response");
327 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700328 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
Fred Quintanaa698f422009-04-08 19:14:54 -0700329 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700330 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700331 }
332
Fred Quintana756b7352009-10-21 13:43:10 -0700333 /**
334 * Removes the given authtoken. If this authtoken does not exist for the given account type
335 * then this call has no effect.
336 * <p>
337 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
338 * @param accountType the account type of the authtoken to invalidate
339 * @param authToken the authtoken to invalidate
340 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700341 public void invalidateAuthToken(final String accountType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700342 try {
343 mService.invalidateAuthToken(accountType, authToken);
344 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700345 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700346 throw new RuntimeException(e);
347 }
348 }
349
Fred Quintana756b7352009-10-21 13:43:10 -0700350 /**
351 * Gets the authtoken named by "authTokenType" for the specified account if it is cached
352 * by the AccountManager. If no authtoken is cached then null is returned rather than
353 * asking the authenticaticor to generate one. If the account or the
354 * authtoken do not exist then null is returned.
355 * <p>
356 * Requires that the caller has permission
357 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
358 * with the same UID as the Authenticator for the account.
359 * @param account the account whose authtoken is to be retrieved, must not be null
360 * @param authTokenType the type of authtoken to retrieve
361 * @return an authtoken for the given account and authTokenType, if one is cached by the
362 * AccountManager, null otherwise.
363 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700364 public String peekAuthToken(final Account account, final String authTokenType) {
Fred Quintana31957f12009-10-21 13:43:10 -0700365 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700366 Log.e(TAG, "peekAuthToken: the account must not be null");
367 return null;
Fred Quintana31957f12009-10-21 13:43:10 -0700368 }
369 if (authTokenType == null) {
370 return null;
371 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700372 try {
373 return mService.peekAuthToken(account, authTokenType);
374 } catch (RemoteException e) {
375 // won't ever happen
376 throw new RuntimeException(e);
377 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700378 }
379
Fred Quintana756b7352009-10-21 13:43:10 -0700380 /**
381 * Sets the password for the account. The password may be null. If the account does not exist
382 * then this call has no affect.
383 * <p>
384 * Requires that the caller has permission
385 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
386 * with the same UID as the Authenticator for the account.
387 * @param account the account whose password is to be set. Must not be null.
388 * @param password the password to set for the account. May be null.
389 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700390 public void setPassword(final Account account, final String password) {
Fred Quintana31957f12009-10-21 13:43:10 -0700391 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700392 Log.e(TAG, "the account must not be null");
393 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700394 }
Fred Quintana60307342009-03-24 22:48:12 -0700395 try {
396 mService.setPassword(account, password);
397 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700398 // won't ever happen
399 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700400 }
401 }
402
Fred Quintana756b7352009-10-21 13:43:10 -0700403 /**
404 * Sets the password for account to null. If the account does not exist then this call
405 * has no effect.
406 * <p>
407 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
408 * @param account the account whose password is to be cleared. Must not be null.
409 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700410 public void clearPassword(final Account account) {
Fred Quintana31957f12009-10-21 13:43:10 -0700411 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700412 Log.e(TAG, "the account must not be null");
413 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700414 }
Fred Quintana60307342009-03-24 22:48:12 -0700415 try {
416 mService.clearPassword(account);
417 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700418 // won't ever happen
419 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700420 }
421 }
422
Fred Quintana756b7352009-10-21 13:43:10 -0700423 /**
424 * Sets account's userdata named "key" to the specified value. If the account does not
425 * exist then this call has no effect.
426 * <p>
427 * Requires that the caller has permission
428 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
429 * with the same UID as the Authenticator for the account.
430 * @param account the account whose userdata is to be set. Must not be null.
431 * @param key the key of the userdata to set. Must not be null.
432 * @param value the value to set. May be null.
433 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700434 public void setUserData(final Account account, final String key, final String value) {
Fred Quintana31957f12009-10-21 13:43:10 -0700435 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700436 Log.e(TAG, "the account must not be null");
437 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700438 }
439 if (key == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700440 Log.e(TAG, "the key must not be null");
441 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700442 }
Fred Quintana60307342009-03-24 22:48:12 -0700443 try {
444 mService.setUserData(account, key, value);
445 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700446 // won't ever happen
447 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700448 }
449 }
450
Fred Quintana756b7352009-10-21 13:43:10 -0700451 /**
452 * Sets the authtoken named by "authTokenType" to the value specified by authToken.
453 * If the account does not exist then this call has no effect.
454 * <p>
455 * Requires that the caller has permission
456 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
457 * with the same UID as the Authenticator for the account.
458 * @param account the account whose authtoken is to be set. Must not be null.
459 * @param authTokenType the type of the authtoken to set. Must not be null.
460 * @param authToken the authToken to set. May be null.
461 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700462 public void setAuthToken(Account account, final String authTokenType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700463 try {
464 mService.setAuthToken(account, authTokenType, authToken);
465 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700466 // won't ever happen
467 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700468 }
469 }
470
Fred Quintana756b7352009-10-21 13:43:10 -0700471 /**
472 * Convenience method that makes a blocking call to
473 * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
474 * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
475 * <p>
476 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
477 * @param account the account whose authtoken is to be retrieved, must not be null
478 * @param authTokenType the type of authtoken to retrieve
479 * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
480 * for the account if no authtoken is cached by the AccountManager and the the authenticator
481 * does not have valid credentials to get an authtoken.
482 * @return an authtoken for the given account and authTokenType, if one is cached by the
483 * AccountManager, null otherwise.
484 * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
485 * an invalid response.
486 * @throws OperationCanceledException if the request is canceled for any reason
487 * @throws java.io.IOException if the authenticator experiences an IOException while attempting
488 * to communicate with its backend server.
489 */
Fred Quintanaa698f422009-04-08 19:14:54 -0700490 public String blockingGetAuthToken(Account account, String authTokenType,
491 boolean notifyAuthFailure)
492 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaa698f422009-04-08 19:14:54 -0700493 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
494 null /* handler */).getResult();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700495 return bundle.getString(KEY_AUTHTOKEN);
Fred Quintanaa698f422009-04-08 19:14:54 -0700496 }
497
498 /**
Fred Quintana756b7352009-10-21 13:43:10 -0700499 * Request that an authtoken of the specified type be returned for an account.
500 * If the Account Manager has a cached authtoken of the requested type then it will
501 * service the request itself. Otherwise it will pass the request on to the authenticator.
502 * The authenticator can try to service this request with information it already has stored
503 * in the AccountManager but may need to launch an activity to prompt the
504 * user to enter credentials. If it is able to retrieve the authtoken it will be returned
505 * in the result.
506 * <p>
507 * If the authenticator needs to prompt the user for credentials it will return an intent to
508 * the activity that will do the prompting. If an activity is supplied then that activity
509 * will be used to launch the intent and the result will come from it. Otherwise a result will
510 * be returned that contains the intent.
511 * <p>
512 * This call returns immediately but runs asynchronously and the result is accessed via the
513 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
514 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
515 * method asynchronously then they will generally pass in a callback object that will get
516 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
517 * they will generally pass null for the callback and instead call
518 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
519 * which will then block until the request completes.
520 * <p>
521 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
Fred Quintanaa698f422009-04-08 19:14:54 -0700522 *
Fred Quintana756b7352009-10-21 13:43:10 -0700523 * @param account The account whose credentials are to be updated.
524 * @param authTokenType the auth token to retrieve as part of updating the credentials.
525 * May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700526 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700527 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
528 * the intent will be started with this activity. If activity is null then the result will
529 * be returned as-is.
530 * @param callback A callback to invoke when the request completes. If null then
531 * no callback is invoked.
532 * @param handler The {@link Handler} to use to invoke the callback. If null then the
533 * main thread's {@link Handler} is used.
534 * @return an {@link AccountManagerFuture} that represents the future result of the call.
535 * The future result is a {@link Bundle} that contains:
536 * <ul>
537 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
538 * </ul>
539 * If the user presses "back" then the request will be canceled.
Fred Quintanaa698f422009-04-08 19:14:54 -0700540 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700541 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintana31957f12009-10-21 13:43:10 -0700542 final Account account, final String authTokenType, final Bundle options,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700543 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700544 if (activity == null) throw new IllegalArgumentException("activity is null");
545 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
546 return new AmsTask(activity, handler, callback) {
547 public void doWork() throws RemoteException {
548 mService.getAuthToken(mResponse, account, authTokenType,
549 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
Fred Quintana31957f12009-10-21 13:43:10 -0700550 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700551 }
Fred Quintana33269202009-04-20 16:05:10 -0700552 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700553 }
554
Fred Quintana756b7352009-10-21 13:43:10 -0700555 /**
556 * Request that an authtoken of the specified type be returned for an account.
557 * If the Account Manager has a cached authtoken of the requested type then it will
558 * service the request itself. Otherwise it will pass the request on to the authenticator.
559 * The authenticator can try to service this request with information it already has stored
560 * in the AccountManager but may need to launch an activity to prompt the
561 * user to enter credentials. If it is able to retrieve the authtoken it will be returned
562 * in the result.
563 * <p>
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800564 * If the authenticator needs to prompt the user for credentials, rather than returning the
565 * authtoken it will instead return an intent for
Fred Quintana756b7352009-10-21 13:43:10 -0700566 * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800567 * is true then a notification will be created that launches this intent. This intent can be
568 * invoked by the caller directly to start the activity that prompts the user for the
569 * updated credentials. Otherwise this activity will not be run until the user activates
570 * the notification.
Fred Quintana756b7352009-10-21 13:43:10 -0700571 * <p>
572 * This call returns immediately but runs asynchronously and the result is accessed via the
573 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
574 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
575 * method asynchronously then they will generally pass in a callback object that will get
576 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
577 * they will generally pass null for the callback and instead call
578 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
579 * which will then block until the request completes.
580 * <p>
581 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
582 *
583 * @param account The account whose credentials are to be updated.
584 * @param authTokenType the auth token to retrieve as part of updating the credentials.
585 * May be null.
586 * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
587 * result then a "sign-on needed" notification will be created that will launch this intent.
588 * @param callback A callback to invoke when the request completes. If null then
589 * no callback is invoked.
590 * @param handler The {@link Handler} to use to invoke the callback. If null then the
591 * main thread's {@link Handler} is used.
592 * @return an {@link AccountManagerFuture} that represents the future result of the call.
593 * The future result is a {@link Bundle} that contains either:
594 * <ul>
595 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
596 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
597 * if the authenticator is able to retrieve the auth token
598 * </ul>
599 * If the user presses "back" then the request will be canceled.
600 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700601 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintanaa698f422009-04-08 19:14:54 -0700602 final Account account, final String authTokenType, final boolean notifyAuthFailure,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700603 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700604 if (account == null) throw new IllegalArgumentException("account is null");
605 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
606 return new AmsTask(null, handler, callback) {
607 public void doWork() throws RemoteException {
608 mService.getAuthToken(mResponse, account, authTokenType,
609 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
610 }
Fred Quintana33269202009-04-20 16:05:10 -0700611 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700612 }
613
Fred Quintana756b7352009-10-21 13:43:10 -0700614 /**
615 * Request that an account be added with the given accountType. This request
616 * is processed by the authenticator for the account type. If no authenticator is registered
617 * in the system then {@link AuthenticatorException} is thrown.
618 * <p>
619 * This call returns immediately but runs asynchronously and the result is accessed via the
620 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
621 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
622 * method asynchronously then they will generally pass in a callback object that will get
623 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
624 * they will generally pass null for the callback and instead call
625 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
626 * which will then block until the request completes.
627 * <p>
628 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
629 *
630 * @param accountType The type of account to add. This must not be null.
631 * @param authTokenType The account that is added should be able to service this auth token
632 * type. This may be null.
633 * @param requiredFeatures The account that is added should support these features.
634 * This array may be null or empty.
635 * @param addAccountOptions A bundle of authenticator-specific options that is passed on
636 * to the authenticator. This may be null.
637 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
638 * the intent will be started with this activity. If activity is null then the result will
639 * be returned as-is.
640 * @param callback A callback to invoke when the request completes. If null then
641 * no callback is invoked.
642 * @param handler The {@link Handler} to use to invoke the callback. If null then the
643 * main thread's {@link Handler} is used.
644 * @return an {@link AccountManagerFuture} that represents the future result of the call.
645 * The future result is a {@link Bundle} that contains either:
646 * <ul>
647 * <li> {@link #KEY_INTENT}, or
648 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
649 * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
650 * </ul>
651 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700652 public AccountManagerFuture<Bundle> addAccount(final String accountType,
Fred Quintana33269202009-04-20 16:05:10 -0700653 final String authTokenType, final String[] requiredFeatures,
654 final Bundle addAccountOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700655 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700656 return new AmsTask(activity, handler, callback) {
657 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700658 if (accountType == null) {
659 Log.e(TAG, "the account must not be null");
660 // to unblock caller waiting on Future.get()
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800661 set(new Bundle());
Costin Manolache88a211b2009-10-29 11:30:11 -0700662 return;
663 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700664 mService.addAcount(mResponse, accountType, authTokenType,
Fred Quintana33269202009-04-20 16:05:10 -0700665 requiredFeatures, activity != null, addAccountOptions);
Fred Quintanaa698f422009-04-08 19:14:54 -0700666 }
Fred Quintana33269202009-04-20 16:05:10 -0700667 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700668 }
669
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700670 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
671 final String type, final String[] features,
672 AccountManagerCallback<Account[]> callback, Handler handler) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700673 return new Future2Task<Account[]>(handler, callback) {
674 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700675 if (type == null) {
676 Log.e(TAG, "Type is null");
677 set(new Account[0]);
678 return;
679 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700680 mService.getAccountsByFeatures(mResponse, type, features);
681 }
682 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700683 if (!bundle.containsKey(KEY_ACCOUNTS)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700684 throw new AuthenticatorException("no result in response");
685 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700686 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700687 Account[] descs = new Account[parcelables.length];
688 for (int i = 0; i < parcelables.length; i++) {
689 descs[i] = (Account) parcelables[i];
690 }
691 return descs;
692 }
693 }.start();
694 }
695
Fred Quintana756b7352009-10-21 13:43:10 -0700696 /**
697 * Requests that the authenticator checks that the user knows the credentials for the account.
698 * This is typically done by returning an intent to an activity that prompts the user to
699 * enter the credentials. This request
700 * is processed by the authenticator for the account. If no matching authenticator is
701 * registered in the system then {@link AuthenticatorException} is thrown.
702 * <p>
703 * This call returns immediately but runs asynchronously and the result is accessed via the
704 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
705 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
706 * method asynchronously then they will generally pass in a callback object that will get
707 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
708 * they will generally pass null for the callback and instead call
709 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
710 * which will then block until the request completes.
711 * <p>
712 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
713 *
714 * @param account The account whose credentials are to be checked
715 * @param options authenticator specific options for the request
716 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
717 * the intent will be started with this activity. If activity is null then the result will
718 * be returned as-is.
719 * @param callback A callback to invoke when the request completes. If null then
720 * no callback is invoked.
721 * @param handler The {@link Handler} to use to invoke the callback. If null then the
722 * main thread's {@link Handler} is used.
723 * @return an {@link AccountManagerFuture} that represents the future result of the call.
724 * The future result is a {@link Bundle} that contains either:
725 * <ul>
726 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
727 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
728 * credentials
729 * </ul>
730 * If the user presses "back" then the request will be canceled.
731 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700732 public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
733 final Bundle options,
734 final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700735 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700736 final Handler handler) {
737 return new AmsTask(activity, handler, callback) {
738 public void doWork() throws RemoteException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700739 mService.confirmCredentials(mResponse, account, options, activity != null);
Fred Quintanaa698f422009-04-08 19:14:54 -0700740 }
Fred Quintana33269202009-04-20 16:05:10 -0700741 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700742 }
743
Fred Quintana756b7352009-10-21 13:43:10 -0700744 /**
745 * Requests that the authenticator update the the credentials for a user. This is typically
746 * done by returning an intent to an activity that will prompt the user to update the stored
747 * credentials for the account. This request
748 * is processed by the authenticator for the account. If no matching authenticator is
749 * registered in the system then {@link AuthenticatorException} is thrown.
750 * <p>
751 * This call returns immediately but runs asynchronously and the result is accessed via the
752 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
753 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
754 * method asynchronously then they will generally pass in a callback object that will get
755 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
756 * they will generally pass null for the callback and instead call
757 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
758 * which will then block until the request completes.
759 * <p>
760 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
761 *
762 * @param account The account whose credentials are to be updated.
763 * @param authTokenType the auth token to retrieve as part of updating the credentials.
764 * May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700765 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700766 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
767 * the intent will be started with this activity. If activity is null then the result will
768 * be returned as-is.
769 * @param callback A callback to invoke when the request completes. If null then
770 * no callback is invoked.
771 * @param handler The {@link Handler} to use to invoke the callback. If null then the
772 * main thread's {@link Handler} is used.
773 * @return an {@link AccountManagerFuture} that represents the future result of the call.
774 * The future result is a {@link Bundle} that contains either:
775 * <ul>
776 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
777 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
778 * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided.
779 * </ul>
780 * If the user presses "back" then the request will be canceled.
781 */
782 public AccountManagerFuture<Bundle> updateCredentials(final Account account,
783 final String authTokenType,
Fred Quintana31957f12009-10-21 13:43:10 -0700784 final Bundle options, final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700785 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700786 final Handler handler) {
787 return new AmsTask(activity, handler, callback) {
788 public void doWork() throws RemoteException {
789 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
Fred Quintana31957f12009-10-21 13:43:10 -0700790 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700791 }
Fred Quintana33269202009-04-20 16:05:10 -0700792 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700793 }
794
Fred Quintana756b7352009-10-21 13:43:10 -0700795 /**
796 * Request that the properties for an authenticator be updated. This is typically done by
797 * returning an intent to an activity that will allow the user to make changes. This request
798 * is processed by the authenticator for the account. If no matching authenticator is
799 * registered in the system then {@link AuthenticatorException} is thrown.
800 * <p>
801 * This call returns immediately but runs asynchronously and the result is accessed via the
802 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
803 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
804 * method asynchronously then they will generally pass in a callback object that will get
805 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
806 * they will generally pass null for the callback and instead call
807 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
808 * which will then block until the request completes.
809 * <p>
810 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
811 *
812 * @param accountType The account type of the authenticator whose properties are to be edited.
813 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
814 * the intent will be started with this activity. If activity is null then the result will
815 * be returned as-is.
816 * @param callback A callback to invoke when the request completes. If null then
817 * no callback is invoked.
818 * @param handler The {@link Handler} to use to invoke the callback. If null then the
819 * main thread's {@link Handler} is used.
820 * @return an {@link AccountManagerFuture} that represents the future result of the call.
821 * The future result is a {@link Bundle} that contains either:
822 * <ul>
823 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
824 * <li> nothing, returned if the edit completes successfully
825 * </ul>
826 * If the user presses "back" then the request will be canceled.
827 */
828 public AccountManagerFuture<Bundle> editProperties(final String accountType,
829 final Activity activity, final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700830 final Handler handler) {
831 return new AmsTask(activity, handler, callback) {
832 public void doWork() throws RemoteException {
833 mService.editProperties(mResponse, accountType, activity != null);
834 }
Fred Quintana33269202009-04-20 16:05:10 -0700835 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700836 }
837
838 private void ensureNotOnMainThread() {
839 final Looper looper = Looper.myLooper();
840 if (looper != null && looper == mContext.getMainLooper()) {
Fred Quintana53bd2522010-02-05 15:28:12 -0800841 final IllegalStateException exception = new IllegalStateException(
842 "calling this from your main thread can lead to deadlock");
843 Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
844 exception);
Fred Quintana751fdc02010-02-09 14:13:18 -0800845 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
846 throw exception;
847 }
Fred Quintana60307342009-03-24 22:48:12 -0700848 }
849 }
850
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700851 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
852 final AccountManagerFuture<Bundle> future) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700853 handler = handler == null ? mMainHandler : handler;
854 handler.post(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700855 public void run() {
856 callback.run(future);
857 }
858 });
859 }
Fred Quintana60307342009-03-24 22:48:12 -0700860
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700861 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700862 final Account[] accounts) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700863 final Account[] accountsCopy = new Account[accounts.length];
864 // send a copy to make sure that one doesn't
865 // change what another sees
866 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
867 handler = (handler == null) ? mMainHandler : handler;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700868 handler.post(new Runnable() {
869 public void run() {
Costin Manolacheb6437242009-09-10 16:14:12 -0700870 try {
871 listener.onAccountsUpdated(accountsCopy);
872 } catch (SQLException e) {
873 // Better luck next time. If the problem was disk-full,
874 // the STORAGE_OK intent will re-trigger the update.
875 Log.e(TAG, "Can't update accounts", e);
876 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700877 }
878 });
879 }
880
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700881 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
Fred Quintanaa698f422009-04-08 19:14:54 -0700882 final IAccountManagerResponse mResponse;
883 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700884 final AccountManagerCallback<Bundle> mCallback;
Fred Quintanaa698f422009-04-08 19:14:54 -0700885 final Activity mActivity;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700886 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700887 super(new Callable<Bundle>() {
888 public Bundle call() throws Exception {
889 throw new IllegalStateException("this should never be called");
890 }
891 });
892
893 mHandler = handler;
894 mCallback = callback;
895 mActivity = activity;
896 mResponse = new Response();
Fred Quintana33269202009-04-20 16:05:10 -0700897 }
898
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700899 public final AccountManagerFuture<Bundle> start() {
900 try {
901 doWork();
902 } catch (RemoteException e) {
903 setException(e);
904 }
Fred Quintana33269202009-04-20 16:05:10 -0700905 return this;
Fred Quintana60307342009-03-24 22:48:12 -0700906 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700907
908 public abstract void doWork() throws RemoteException;
909
910 private Bundle internalGetResult(Long timeout, TimeUnit unit)
911 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintana53bd2522010-02-05 15:28:12 -0800912 if (!isDone()) {
913 ensureNotOnMainThread();
914 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700915 try {
916 if (timeout == null) {
917 return get();
918 } else {
919 return get(timeout, unit);
920 }
921 } catch (CancellationException e) {
922 throw new OperationCanceledException();
923 } catch (TimeoutException e) {
924 // fall through and cancel
925 } catch (InterruptedException e) {
926 // fall through and cancel
927 } catch (ExecutionException e) {
928 final Throwable cause = e.getCause();
929 if (cause instanceof IOException) {
930 throw (IOException) cause;
931 } else if (cause instanceof UnsupportedOperationException) {
932 throw new AuthenticatorException(cause);
933 } else if (cause instanceof AuthenticatorException) {
934 throw (AuthenticatorException) cause;
935 } else if (cause instanceof RuntimeException) {
936 throw (RuntimeException) cause;
937 } else if (cause instanceof Error) {
938 throw (Error) cause;
939 } else {
940 throw new IllegalStateException(cause);
941 }
942 } finally {
943 cancel(true /* interrupt if running */);
944 }
945 throw new OperationCanceledException();
946 }
947
948 public Bundle getResult()
949 throws OperationCanceledException, IOException, AuthenticatorException {
950 return internalGetResult(null, null);
951 }
952
953 public Bundle getResult(long timeout, TimeUnit unit)
954 throws OperationCanceledException, IOException, AuthenticatorException {
955 return internalGetResult(timeout, unit);
956 }
957
958 protected void done() {
959 if (mCallback != null) {
960 postToHandler(mHandler, mCallback, this);
961 }
962 }
963
964 /** Handles the responses from the AccountManager */
965 private class Response extends IAccountManagerResponse.Stub {
966 public void onResult(Bundle bundle) {
967 Intent intent = bundle.getParcelable("intent");
968 if (intent != null && mActivity != null) {
969 // since the user provided an Activity we will silently start intents
970 // that we see
971 mActivity.startActivity(intent);
972 // leave the Future running to wait for the real response to this request
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700973 } else if (bundle.getBoolean("retry")) {
974 try {
975 doWork();
976 } catch (RemoteException e) {
977 // this will only happen if the system process is dead, which means
978 // we will be dying ourselves
979 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700980 } else {
981 set(bundle);
982 }
983 }
984
985 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700986 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700987 // the authenticator indicated that this request was canceled, do so now
988 cancel(true /* mayInterruptIfRunning */);
989 return;
990 }
991 setException(convertErrorToException(code, message));
992 }
993 }
994
Fred Quintana60307342009-03-24 22:48:12 -0700995 }
996
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700997 private abstract class BaseFutureTask<T> extends FutureTask<T> {
998 final public IAccountManagerResponse mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700999 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001000
1001 public BaseFutureTask(Handler handler) {
1002 super(new Callable<T>() {
1003 public T call() throws Exception {
Fred Quintanaa698f422009-04-08 19:14:54 -07001004 throw new IllegalStateException("this should never be called");
1005 }
1006 });
Fred Quintanaa698f422009-04-08 19:14:54 -07001007 mHandler = handler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001008 mResponse = new Response();
Fred Quintana60307342009-03-24 22:48:12 -07001009 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001010
1011 public abstract void doWork() throws RemoteException;
1012
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001013 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
Fred Quintanaa698f422009-04-08 19:14:54 -07001014
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001015 protected void postRunnableToHandler(Runnable runnable) {
1016 Handler handler = (mHandler == null) ? mMainHandler : mHandler;
1017 handler.post(runnable);
Fred Quintanaa698f422009-04-08 19:14:54 -07001018 }
1019
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001020 protected void startTask() {
Fred Quintanaa698f422009-04-08 19:14:54 -07001021 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001022 doWork();
1023 } catch (RemoteException e) {
1024 setException(e);
Fred Quintanaa698f422009-04-08 19:14:54 -07001025 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001026 }
1027
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001028 protected class Response extends IAccountManagerResponse.Stub {
Fred Quintanaa698f422009-04-08 19:14:54 -07001029 public void onResult(Bundle bundle) {
1030 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001031 T result = bundleToResult(bundle);
1032 if (result == null) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001033 return;
1034 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001035 set(result);
1036 return;
Fred Quintanaa698f422009-04-08 19:14:54 -07001037 } catch (ClassCastException e) {
1038 // we will set the exception below
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001039 } catch (AuthenticatorException e) {
1040 // we will set the exception below
Fred Quintanaa698f422009-04-08 19:14:54 -07001041 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001042 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
Fred Quintanaa698f422009-04-08 19:14:54 -07001043 }
1044
1045 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001046 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001047 cancel(true /* mayInterruptIfRunning */);
1048 return;
1049 }
1050 setException(convertErrorToException(code, message));
1051 }
1052 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001053 }
1054
1055 private abstract class Future2Task<T>
1056 extends BaseFutureTask<T> implements AccountManagerFuture<T> {
1057 final AccountManagerCallback<T> mCallback;
1058 public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
1059 super(handler);
1060 mCallback = callback;
1061 }
1062
1063 protected void done() {
1064 if (mCallback != null) {
1065 postRunnableToHandler(new Runnable() {
1066 public void run() {
1067 mCallback.run(Future2Task.this);
1068 }
1069 });
1070 }
1071 }
1072
1073 public Future2Task<T> start() {
1074 startTask();
1075 return this;
1076 }
1077
1078 private T internalGetResult(Long timeout, TimeUnit unit)
1079 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintana53bd2522010-02-05 15:28:12 -08001080 if (!isDone()) {
1081 ensureNotOnMainThread();
1082 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001083 try {
1084 if (timeout == null) {
1085 return get();
1086 } else {
1087 return get(timeout, unit);
1088 }
1089 } catch (InterruptedException e) {
1090 // fall through and cancel
1091 } catch (TimeoutException e) {
1092 // fall through and cancel
1093 } catch (CancellationException e) {
1094 // fall through and cancel
1095 } catch (ExecutionException e) {
1096 final Throwable cause = e.getCause();
1097 if (cause instanceof IOException) {
1098 throw (IOException) cause;
1099 } else if (cause instanceof UnsupportedOperationException) {
1100 throw new AuthenticatorException(cause);
1101 } else if (cause instanceof AuthenticatorException) {
1102 throw (AuthenticatorException) cause;
1103 } else if (cause instanceof RuntimeException) {
1104 throw (RuntimeException) cause;
1105 } else if (cause instanceof Error) {
1106 throw (Error) cause;
1107 } else {
1108 throw new IllegalStateException(cause);
1109 }
1110 } finally {
1111 cancel(true /* interrupt if running */);
1112 }
1113 throw new OperationCanceledException();
1114 }
1115
1116 public T getResult()
1117 throws OperationCanceledException, IOException, AuthenticatorException {
1118 return internalGetResult(null, null);
1119 }
1120
1121 public T getResult(long timeout, TimeUnit unit)
1122 throws OperationCanceledException, IOException, AuthenticatorException {
1123 return internalGetResult(timeout, unit);
1124 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001125
Fred Quintana60307342009-03-24 22:48:12 -07001126 }
1127
Fred Quintanaa698f422009-04-08 19:14:54 -07001128 private Exception convertErrorToException(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001129 if (code == ERROR_CODE_NETWORK_ERROR) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001130 return new IOException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001131 }
Fred Quintana60307342009-03-24 22:48:12 -07001132
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001133 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
Fred Quintana33269202009-04-20 16:05:10 -07001134 return new UnsupportedOperationException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001135 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001136
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001137 if (code == ERROR_CODE_INVALID_RESPONSE) {
Fred Quintana33269202009-04-20 16:05:10 -07001138 return new AuthenticatorException(message);
Fred Quintanaa698f422009-04-08 19:14:54 -07001139 }
1140
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001141 if (code == ERROR_CODE_BAD_ARGUMENTS) {
Fred Quintana33269202009-04-20 16:05:10 -07001142 return new IllegalArgumentException(message);
1143 }
1144
1145 return new AuthenticatorException(message);
1146 }
1147
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001148 private class GetAuthTokenByTypeAndFeaturesTask
1149 extends AmsTask implements AccountManagerCallback<Bundle> {
Fred Quintana33269202009-04-20 16:05:10 -07001150 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
1151 final String[] features, Activity activityForPrompting,
1152 final Bundle addAccountOptions, final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001153 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001154 super(activityForPrompting, handler, callback);
1155 if (accountType == null) throw new IllegalArgumentException("account type is null");
1156 mAccountType = accountType;
1157 mAuthTokenType = authTokenType;
1158 mFeatures = features;
1159 mAddAccountOptions = addAccountOptions;
1160 mLoginOptions = loginOptions;
1161 mMyCallback = this;
1162 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001163 volatile AccountManagerFuture<Bundle> mFuture = null;
Fred Quintana33269202009-04-20 16:05:10 -07001164 final String mAccountType;
1165 final String mAuthTokenType;
1166 final String[] mFeatures;
1167 final Bundle mAddAccountOptions;
1168 final Bundle mLoginOptions;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001169 final AccountManagerCallback<Bundle> mMyCallback;
Fred Quintana33269202009-04-20 16:05:10 -07001170
1171 public void doWork() throws RemoteException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001172 getAccountsByTypeAndFeatures(mAccountType, mFeatures,
1173 new AccountManagerCallback<Account[]>() {
1174 public void run(AccountManagerFuture<Account[]> future) {
1175 Account[] accounts;
Fred Quintana33269202009-04-20 16:05:10 -07001176 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001177 accounts = future.getResult();
1178 } catch (OperationCanceledException e) {
1179 setException(e);
1180 return;
1181 } catch (IOException e) {
1182 setException(e);
1183 return;
1184 } catch (AuthenticatorException e) {
1185 setException(e);
1186 return;
Fred Quintana33269202009-04-20 16:05:10 -07001187 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001188
1189 if (accounts.length == 0) {
1190 if (mActivity != null) {
1191 // no accounts, add one now. pretend that the user directly
1192 // made this request
1193 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
1194 mAddAccountOptions, mActivity, mMyCallback, mHandler);
1195 } else {
1196 // send result since we can't prompt to add an account
1197 Bundle result = new Bundle();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001198 result.putString(KEY_ACCOUNT_NAME, null);
1199 result.putString(KEY_ACCOUNT_TYPE, null);
1200 result.putString(KEY_AUTHTOKEN, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001201 try {
1202 mResponse.onResult(result);
1203 } catch (RemoteException e) {
1204 // this will never happen
1205 }
1206 // we are done
1207 }
1208 } else if (accounts.length == 1) {
1209 // have a single account, return an authtoken for it
1210 if (mActivity == null) {
1211 mFuture = getAuthToken(accounts[0], mAuthTokenType,
1212 false /* notifyAuthFailure */, mMyCallback, mHandler);
1213 } else {
1214 mFuture = getAuthToken(accounts[0],
1215 mAuthTokenType, mLoginOptions,
Fred Quintana33269202009-04-20 16:05:10 -07001216 mActivity, mMyCallback, mHandler);
1217 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001218 } else {
1219 if (mActivity != null) {
1220 IAccountManagerResponse chooseResponse =
1221 new IAccountManagerResponse.Stub() {
1222 public void onResult(Bundle value) throws RemoteException {
1223 Account account = new Account(
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001224 value.getString(KEY_ACCOUNT_NAME),
1225 value.getString(KEY_ACCOUNT_TYPE));
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001226 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
1227 mActivity, mMyCallback, mHandler);
1228 }
Fred Quintana33269202009-04-20 16:05:10 -07001229
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001230 public void onError(int errorCode, String errorMessage)
1231 throws RemoteException {
1232 mResponse.onError(errorCode, errorMessage);
1233 }
1234 };
1235 // have many accounts, launch the chooser
1236 Intent intent = new Intent();
1237 intent.setClassName("android",
1238 "android.accounts.ChooseAccountActivity");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001239 intent.putExtra(KEY_ACCOUNTS, accounts);
1240 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001241 new AccountManagerResponse(chooseResponse));
1242 mActivity.startActivity(intent);
1243 // the result will arrive via the IAccountManagerResponse
1244 } else {
1245 // send result since we can't prompt to select an account
1246 Bundle result = new Bundle();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001247 result.putString(KEY_ACCOUNTS, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001248 try {
1249 mResponse.onResult(result);
1250 } catch (RemoteException e) {
1251 // this will never happen
1252 }
1253 // we are done
Fred Quintana33269202009-04-20 16:05:10 -07001254 }
Fred Quintana33269202009-04-20 16:05:10 -07001255 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001256 }}, mHandler);
Fred Quintana33269202009-04-20 16:05:10 -07001257 }
1258
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001259 public void run(AccountManagerFuture<Bundle> future) {
Fred Quintana33269202009-04-20 16:05:10 -07001260 try {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001261 set(future.getResult());
1262 } catch (OperationCanceledException e) {
1263 cancel(true /* mayInterruptIfRUnning */);
1264 } catch (IOException e) {
1265 setException(e);
1266 } catch (AuthenticatorException e) {
1267 setException(e);
Fred Quintana33269202009-04-20 16:05:10 -07001268 }
1269 }
1270 }
1271
Fred Quintana756b7352009-10-21 13:43:10 -07001272 /**
1273 * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
1274 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
1275 * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
1276 * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
1277 * feature set, and addAccountOptions. If there is exactly one then
1278 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
1279 * called with that account. If there are more than one then a chooser activity is launched
1280 * to prompt the user to select one of them and then the authtoken is retrieved for it,
1281 * <p>
1282 * This call returns immediately but runs asynchronously and the result is accessed via the
1283 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
1284 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
1285 * method asynchronously then they will generally pass in a callback object that will get
1286 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
1287 * they will generally pass null for the callback and instead call
1288 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
1289 * which will then block until the request completes.
1290 * <p>
1291 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
1292 *
1293 * @param accountType the accountType to query; this must be non-null
1294 * @param authTokenType the type of authtoken to retrieve; this must be non-null
1295 * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
1296 * @param activityForPrompting The activity used to start any account management
1297 * activities that are required to fulfill this request. This may be null.
1298 * @param addAccountOptions authenticator-specific options used if an account needs to be added
Fred Quintana31957f12009-10-21 13:43:10 -07001299 * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
Fred Quintana756b7352009-10-21 13:43:10 -07001300 * @param callback A callback to invoke when the request completes. If null then
1301 * no callback is invoked.
1302 * @param handler The {@link Handler} to use to invoke the callback. If null then the
1303 * main thread's {@link Handler} is used.
1304 * @return an {@link AccountManagerFuture} that represents the future result of the call.
1305 * The future result is a {@link Bundle} that contains either:
1306 * <ul>
1307 * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
1308 * fulfill the request.
1309 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
1310 * request completes successfully.
1311 * </ul>
1312 * If the user presses "back" then the request will be canceled.
1313 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001314 public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
Fred Quintana33269202009-04-20 16:05:10 -07001315 final String accountType, final String authTokenType, final String[] features,
1316 final Activity activityForPrompting, final Bundle addAccountOptions,
Fred Quintana31957f12009-10-21 13:43:10 -07001317 final Bundle getAuthTokenOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001318 final AccountManagerCallback<Bundle> callback, final Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001319 if (accountType == null) throw new IllegalArgumentException("account type is null");
1320 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001321 final GetAuthTokenByTypeAndFeaturesTask task =
1322 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
Fred Quintana31957f12009-10-21 13:43:10 -07001323 activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001324 task.start();
1325 return task;
Fred Quintana60307342009-03-24 22:48:12 -07001326 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001327
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001328 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Fred Quintanad9d2f112009-04-23 13:36:27 -07001329 Maps.newHashMap();
1330
Fred Quintanad9d2f112009-04-23 13:36:27 -07001331 /**
1332 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
1333 * so that it can read the updated list of accounts and send them to the listener
1334 * in mAccountsUpdatedListeners.
1335 */
1336 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
1337 public void onReceive(final Context context, final Intent intent) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001338 final Account[] accounts = getAccounts();
1339 // send the result to the listeners
1340 synchronized (mAccountsUpdatedListeners) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001341 for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001342 mAccountsUpdatedListeners.entrySet()) {
1343 postToHandler(entry.getValue(), entry.getKey(), accounts);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001344 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001345 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001346 }
1347 };
1348
1349 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001350 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001351 * The listener is guaranteed to be invoked on the thread of the Handler that is passed
1352 * in or the main thread's Handler if handler is null.
Fred Quintanae00a3112009-09-22 15:13:30 -07001353 * <p>
1354 * You must remove this listener before the context that was used to retrieve this
1355 * {@link AccountManager} instance goes away. This generally means when the Activity
1356 * or Service you are running is stopped.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001357 * @param listener the listener to add
1358 * @param handler the Handler whose thread will be used to invoke the listener. If null
1359 * the AccountManager context's main thread will be used.
1360 * @param updateImmediately if true then the listener will be invoked as a result of this
1361 * call.
1362 * @throws IllegalArgumentException if listener is null
1363 * @throws IllegalStateException if listener was already added
1364 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001365 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
Fred Quintanad9d2f112009-04-23 13:36:27 -07001366 Handler handler, boolean updateImmediately) {
1367 if (listener == null) {
1368 throw new IllegalArgumentException("the listener is null");
1369 }
1370 synchronized (mAccountsUpdatedListeners) {
1371 if (mAccountsUpdatedListeners.containsKey(listener)) {
1372 throw new IllegalStateException("this listener is already added");
1373 }
1374 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
1375
1376 mAccountsUpdatedListeners.put(listener, handler);
1377
1378 if (wasEmpty) {
1379 // Register a broadcast receiver to monitor account changes
1380 IntentFilter intentFilter = new IntentFilter();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001381 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
Costin Manolacheb6437242009-09-10 16:14:12 -07001382 // To recover from disk-full.
Fred Quintanac5d1c6d2010-01-27 12:17:49 -08001383 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001384 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1385 }
1386 }
1387
1388 if (updateImmediately) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001389 postToHandler(handler, listener, getAccounts());
Fred Quintanad9d2f112009-04-23 13:36:27 -07001390 }
1391 }
1392
1393 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001394 * Remove an {@link OnAccountsUpdateListener} that was previously registered with
Fred Quintanad9d2f112009-04-23 13:36:27 -07001395 * {@link #addOnAccountsUpdatedListener}.
1396 * @param listener the listener to remove
1397 * @throws IllegalArgumentException if listener is null
1398 * @throws IllegalStateException if listener was not already added
1399 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001400 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
Fred Quintanad9d2f112009-04-23 13:36:27 -07001401 if (listener == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001402 Log.e(TAG, "Missing listener");
1403 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001404 }
1405 synchronized (mAccountsUpdatedListeners) {
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001406 if (!mAccountsUpdatedListeners.containsKey(listener)) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001407 Log.e(TAG, "Listener was not previously added");
1408 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001409 }
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001410 mAccountsUpdatedListeners.remove(listener);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001411 if (mAccountsUpdatedListeners.isEmpty()) {
1412 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1413 }
1414 }
1415 }
Fred Quintana60307342009-03-24 22:48:12 -07001416}