blob: 9765496762c031a668ebd5ac35ffc1452a4f19c9 [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;
Costin Manolacheb6437242009-09-10 16:14:12 -070030import android.util.Log;
Fred Quintana60307342009-03-24 22:48:12 -070031
Fred Quintanaa698f422009-04-08 19:14:54 -070032import java.io.IOException;
33import java.util.concurrent.Callable;
34import java.util.concurrent.CancellationException;
35import java.util.concurrent.ExecutionException;
36import java.util.concurrent.FutureTask;
37import java.util.concurrent.TimeoutException;
38import java.util.concurrent.TimeUnit;
Fred Quintanad9d2f112009-04-23 13:36:27 -070039import java.util.HashMap;
40import java.util.Map;
41
42import com.google.android.collect.Maps;
Fred Quintana60307342009-03-24 22:48:12 -070043
44/**
Fred Quintana756b7352009-10-21 13:43:10 -070045 * A class that helps with interactions with the AccountManager Service. It provides
Fred Quintana60307342009-03-24 22:48:12 -070046 * methods to allow for account, password, and authtoken management for all accounts on the
Fred Quintana4db3a5b2009-10-05 17:19:03 -070047 * device. One accesses the {@link AccountManager} by calling:
Fred Quintana756b7352009-10-21 13:43:10 -070048 * <pre>
Fred Quintanaa698f422009-04-08 19:14:54 -070049 * AccountManager accountManager = AccountManager.get(context);
Fred Quintana756b7352009-10-21 13:43:10 -070050 * </pre>
Fred Quintana60307342009-03-24 22:48:12 -070051 *
52 * <p>
Fred Quintana756b7352009-10-21 13:43:10 -070053 * 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.
Fred Quintana60307342009-03-24 22:48:12 -070073 */
74public class AccountManager {
75 private static final String TAG = "AccountManager";
76
Fred Quintanaf7ae77c2009-10-02 17:19:31 -070077 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;
Fred Quintana756b7352009-10-21 13:43:10 -070084
Fred Quintanaf7ae77c2009-10-02 17:19:31 -070085 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
Fred Quintana60307342009-03-24 22:48:12 -0700106 private final Context mContext;
107 private final IAccountManager mService;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700108 private final Handler mMainHandler;
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700109 /**
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";
Fred Quintana60307342009-03-24 22:48:12 -0700116
Fred Quintana33269202009-04-20 16:05:10 -0700117 /**
118 * @hide
119 */
Fred Quintana60307342009-03-24 22:48:12 -0700120 public AccountManager(Context context, IAccountManager service) {
121 mContext = context;
122 mService = service;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700123 mMainHandler = new Handler(mContext.getMainLooper());
Fred Quintana60307342009-03-24 22:48:12 -0700124 }
125
Fred Quintana0eabf022009-04-27 15:08:17 -0700126 /**
127 * @hide used for testing only
128 */
129 public AccountManager(Context context, IAccountManager service, Handler handler) {
130 mContext = context;
131 mService = service;
132 mMainHandler = handler;
133 }
134
Fred Quintana756b7352009-10-21 13:43:10 -0700135 /**
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 */
Fred Quintanaa698f422009-04-08 19:14:54 -0700143 public static AccountManager get(Context context) {
144 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
145 }
146
Fred Quintana756b7352009-10-21 13:43:10 -0700147 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700155 public String getPassword(final Account account) {
Fred Quintana60307342009-03-24 22:48:12 -0700156 try {
157 return mService.getPassword(account);
158 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700159 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -0700160 throw new RuntimeException(e);
161 }
162 }
163
Fred Quintana756b7352009-10-21 13:43:10 -0700164 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700172 public String getUserData(final Account account, final String key) {
Fred Quintana60307342009-03-24 22:48:12 -0700173 try {
174 return mService.getUserData(account, key);
175 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700176 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -0700177 throw new RuntimeException(e);
178 }
179 }
180
Fred Quintana756b7352009-10-21 13:43:10 -0700181 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700189 public AuthenticatorDescription[] getAuthenticatorTypes() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700190 try {
191 return mService.getAuthenticatorTypes();
192 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700193 // will never happen
Fred Quintanaa698f422009-04-08 19:14:54 -0700194 throw new RuntimeException(e);
195 }
196 }
197
Fred Quintana756b7352009-10-21 13:43:10 -0700198 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700205 public Account[] getAccounts() {
Fred Quintana60307342009-03-24 22:48:12 -0700206 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700207 return mService.getAccounts(null);
Fred Quintana60307342009-03-24 22:48:12 -0700208 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700209 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700210 throw new RuntimeException(e);
211 }
212 }
213
Fred Quintana756b7352009-10-21 13:43:10 -0700214 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700223 public Account[] getAccountsByType(String type) {
Fred Quintana60307342009-03-24 22:48:12 -0700224 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700225 return mService.getAccounts(type);
Fred Quintana60307342009-03-24 22:48:12 -0700226 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700227 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700228 throw new RuntimeException(e);
229 }
230 }
231
Fred Quintana756b7352009-10-21 13:43:10 -0700232 /**
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.
Fred Quintana31957f12009-10-21 13:43:10 -0700240 * @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 -0700241 * @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 */
Fred Quintana31957f12009-10-21 13:43:10 -0700244 public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
Fred Quintana60307342009-03-24 22:48:12 -0700245 try {
Fred Quintana31957f12009-10-21 13:43:10 -0700246 return mService.addAccount(account, password, userdata);
Fred Quintana60307342009-03-24 22:48:12 -0700247 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700248 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700249 throw new RuntimeException(e);
250 }
251 }
252
Fred Quintana756b7352009-10-21 13:43:10 -0700253 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700276 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);
Fred Quintanaa698f422009-04-08 19:14:54 -0700281 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700282 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700283 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700284 throw new AuthenticatorException("no result in response");
285 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700286 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
Fred Quintanaa698f422009-04-08 19:14:54 -0700287 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700288 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700289 }
290
Fred Quintana756b7352009-10-21 13:43:10 -0700291 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700299 public void invalidateAuthToken(final String accountType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700300 try {
301 mService.invalidateAuthToken(accountType, authToken);
302 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700303 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700304 throw new RuntimeException(e);
305 }
306 }
307
Fred Quintana756b7352009-10-21 13:43:10 -0700308 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700322 public String peekAuthToken(final Account account, final String authTokenType) {
Fred Quintana31957f12009-10-21 13:43:10 -0700323 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700324 Log.e(TAG, "peekAuthToken: the account must not be null");
325 return null;
Fred Quintana31957f12009-10-21 13:43:10 -0700326 }
327 if (authTokenType == null) {
328 return null;
329 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700330 try {
331 return mService.peekAuthToken(account, authTokenType);
332 } catch (RemoteException e) {
333 // won't ever happen
334 throw new RuntimeException(e);
335 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700336 }
337
Fred Quintana756b7352009-10-21 13:43:10 -0700338 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700348 public void setPassword(final Account account, final String password) {
Fred Quintana31957f12009-10-21 13:43:10 -0700349 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700350 Log.e(TAG, "the account must not be null");
351 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700352 }
Fred Quintana60307342009-03-24 22:48:12 -0700353 try {
354 mService.setPassword(account, password);
355 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700356 // won't ever happen
357 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700358 }
359 }
360
Fred Quintana756b7352009-10-21 13:43:10 -0700361 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700368 public void clearPassword(final Account account) {
Fred Quintana31957f12009-10-21 13:43:10 -0700369 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700370 Log.e(TAG, "the account must not be null");
371 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700372 }
Fred Quintana60307342009-03-24 22:48:12 -0700373 try {
374 mService.clearPassword(account);
375 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700376 // won't ever happen
377 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700378 }
379 }
380
Fred Quintana756b7352009-10-21 13:43:10 -0700381 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700392 public void setUserData(final Account account, final String key, final String value) {
Fred Quintana31957f12009-10-21 13:43:10 -0700393 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700394 Log.e(TAG, "the account must not be null");
395 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700396 }
397 if (key == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700398 Log.e(TAG, "the key must not be null");
399 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700400 }
Fred Quintana60307342009-03-24 22:48:12 -0700401 try {
402 mService.setUserData(account, key, value);
403 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700404 // won't ever happen
405 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700406 }
407 }
408
Fred Quintana756b7352009-10-21 13:43:10 -0700409 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700420 public void setAuthToken(Account account, final String authTokenType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700421 try {
422 mService.setAuthToken(account, authTokenType, authToken);
423 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700424 // won't ever happen
425 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700426 }
427 }
428
Fred Quintana756b7352009-10-21 13:43:10 -0700429 /**
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 */
Fred Quintanaa698f422009-04-08 19:14:54 -0700448 public String blockingGetAuthToken(Account account, String authTokenType,
449 boolean notifyAuthFailure)
450 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaa698f422009-04-08 19:14:54 -0700451 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
452 null /* handler */).getResult();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700453 return bundle.getString(KEY_AUTHTOKEN);
Fred Quintanaa698f422009-04-08 19:14:54 -0700454 }
455
456 /**
Fred Quintana756b7352009-10-21 13:43:10 -0700457 * 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}.
Fred Quintanaa698f422009-04-08 19:14:54 -0700480 *
Fred Quintana756b7352009-10-21 13:43:10 -0700481 * @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.
Fred Quintana31957f12009-10-21 13:43:10 -0700484 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700485 * @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.
Fred Quintanaa698f422009-04-08 19:14:54 -0700498 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700499 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintana31957f12009-10-21 13:43:10 -0700500 final Account account, final String authTokenType, final Bundle options,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700501 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700502 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 */,
Fred Quintana31957f12009-10-21 13:43:10 -0700508 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700509 }
Fred Quintana33269202009-04-20 16:05:10 -0700510 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700511 }
512
Fred Quintana756b7352009-10-21 13:43:10 -0700513 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700555 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintanaa698f422009-04-08 19:14:54 -0700556 final Account account, final String authTokenType, final boolean notifyAuthFailure,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700557 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700558 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 }
Fred Quintana33269202009-04-20 16:05:10 -0700565 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700566 }
567
Fred Quintana756b7352009-10-21 13:43:10 -0700568 /**
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 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700606 public AccountManagerFuture<Bundle> addAccount(final String accountType,
Fred Quintana33269202009-04-20 16:05:10 -0700607 final String authTokenType, final String[] requiredFeatures,
608 final Bundle addAccountOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700609 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700610 return new AmsTask(activity, handler, callback) {
611 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700612 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 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700618 mService.addAcount(mResponse, accountType, authTokenType,
Fred Quintana33269202009-04-20 16:05:10 -0700619 requiredFeatures, activity != null, addAccountOptions);
Fred Quintanaa698f422009-04-08 19:14:54 -0700620 }
Fred Quintana33269202009-04-20 16:05:10 -0700621 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700622 }
623
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700624 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
625 final String type, final String[] features,
626 AccountManagerCallback<Account[]> callback, Handler handler) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700627 return new Future2Task<Account[]>(handler, callback) {
628 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700629 if (type == null) {
630 Log.e(TAG, "Type is null");
631 set(new Account[0]);
632 return;
633 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700634 mService.getAccountsByFeatures(mResponse, type, features);
635 }
636 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700637 if (!bundle.containsKey(KEY_ACCOUNTS)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700638 throw new AuthenticatorException("no result in response");
639 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700640 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700641 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
Fred Quintana756b7352009-10-21 13:43:10 -0700650 /**
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 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700686 public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
687 final Bundle options,
688 final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700689 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700690 final Handler handler) {
691 return new AmsTask(activity, handler, callback) {
692 public void doWork() throws RemoteException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700693 mService.confirmCredentials(mResponse, account, options, activity != null);
Fred Quintanaa698f422009-04-08 19:14:54 -0700694 }
Fred Quintana33269202009-04-20 16:05:10 -0700695 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700696 }
697
Fred Quintana756b7352009-10-21 13:43:10 -0700698 /**
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.
Fred Quintana31957f12009-10-21 13:43:10 -0700719 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700720 * @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,
Fred Quintana31957f12009-10-21 13:43:10 -0700738 final Bundle options, final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700739 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700740 final Handler handler) {
741 return new AmsTask(activity, handler, callback) {
742 public void doWork() throws RemoteException {
743 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
Fred Quintana31957f12009-10-21 13:43:10 -0700744 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700745 }
Fred Quintana33269202009-04-20 16:05:10 -0700746 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700747 }
748
Fred Quintana756b7352009-10-21 13:43:10 -0700749 /**
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,
Fred Quintanaa698f422009-04-08 19:14:54 -0700784 final Handler handler) {
785 return new AmsTask(activity, handler, callback) {
786 public void doWork() throws RemoteException {
787 mService.editProperties(mResponse, accountType, activity != null);
788 }
Fred Quintana33269202009-04-20 16:05:10 -0700789 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700790 }
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());
Costin Manolache88a211b2009-10-29 11:30:11 -0700800 // TODO remove the log and throw this exception when the callers are fixed
Fred Quintanaa698f422009-04-08 19:14:54 -0700801// throw new IllegalStateException(
802// "calling this from your main thread can lead to deadlock");
Fred Quintana60307342009-03-24 22:48:12 -0700803 }
804 }
805
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700806 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
807 final AccountManagerFuture<Bundle> future) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700808 handler = handler == null ? mMainHandler : handler;
809 handler.post(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700810 public void run() {
811 callback.run(future);
812 }
813 });
814 }
Fred Quintana60307342009-03-24 22:48:12 -0700815
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700816 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700817 final Account[] accounts) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700818 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;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700823 handler.post(new Runnable() {
824 public void run() {
Costin Manolacheb6437242009-09-10 16:14:12 -0700825 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 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700832 }
833 });
834 }
835
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700836 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
Fred Quintanaa698f422009-04-08 19:14:54 -0700837 final IAccountManagerResponse mResponse;
838 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700839 final AccountManagerCallback<Bundle> mCallback;
Fred Quintanaa698f422009-04-08 19:14:54 -0700840 final Activity mActivity;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700841 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700842 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();
Fred Quintana33269202009-04-20 16:05:10 -0700852 }
853
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700854 public final AccountManagerFuture<Bundle> start() {
855 try {
856 doWork();
857 } catch (RemoteException e) {
858 setException(e);
859 }
Fred Quintana33269202009-04-20 16:05:10 -0700860 return this;
Fred Quintana60307342009-03-24 22:48:12 -0700861 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700862
863 public abstract void doWork() throws RemoteException;
864
865 private Bundle internalGetResult(Long timeout, TimeUnit unit)
866 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700867 ensureNotOnMainThread();
Fred Quintanaa698f422009-04-08 19:14:54 -0700868 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
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700926 } 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 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700933 } else {
934 set(bundle);
935 }
936 }
937
938 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700939 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700940 // 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
Fred Quintana60307342009-03-24 22:48:12 -0700948 }
949
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700950 private abstract class BaseFutureTask<T> extends FutureTask<T> {
951 final public IAccountManagerResponse mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700952 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700953
954 public BaseFutureTask(Handler handler) {
955 super(new Callable<T>() {
956 public T call() throws Exception {
Fred Quintanaa698f422009-04-08 19:14:54 -0700957 throw new IllegalStateException("this should never be called");
958 }
959 });
Fred Quintanaa698f422009-04-08 19:14:54 -0700960 mHandler = handler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700961 mResponse = new Response();
Fred Quintana60307342009-03-24 22:48:12 -0700962 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700963
964 public abstract void doWork() throws RemoteException;
965
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700966 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
Fred Quintanaa698f422009-04-08 19:14:54 -0700967
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700968 protected void postRunnableToHandler(Runnable runnable) {
969 Handler handler = (mHandler == null) ? mMainHandler : mHandler;
970 handler.post(runnable);
Fred Quintanaa698f422009-04-08 19:14:54 -0700971 }
972
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700973 protected void startTask() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700974 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700975 doWork();
976 } catch (RemoteException e) {
977 setException(e);
Fred Quintanaa698f422009-04-08 19:14:54 -0700978 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700979 }
980
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700981 protected class Response extends IAccountManagerResponse.Stub {
Fred Quintanaa698f422009-04-08 19:14:54 -0700982 public void onResult(Bundle bundle) {
983 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700984 T result = bundleToResult(bundle);
985 if (result == null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700986 return;
987 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700988 set(result);
989 return;
Fred Quintanaa698f422009-04-08 19:14:54 -0700990 } catch (ClassCastException e) {
991 // we will set the exception below
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700992 } catch (AuthenticatorException e) {
993 // we will set the exception below
Fred Quintanaa698f422009-04-08 19:14:54 -0700994 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700995 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
Fred Quintanaa698f422009-04-08 19:14:54 -0700996 }
997
998 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700999 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001000 cancel(true /* mayInterruptIfRunning */);
1001 return;
1002 }
1003 setException(convertErrorToException(code, message));
1004 }
1005 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001006 }
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 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001076
Fred Quintana60307342009-03-24 22:48:12 -07001077 }
1078
Fred Quintanaa698f422009-04-08 19:14:54 -07001079 private Exception convertErrorToException(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001080 if (code == ERROR_CODE_NETWORK_ERROR) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001081 return new IOException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001082 }
Fred Quintana60307342009-03-24 22:48:12 -07001083
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001084 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
Fred Quintana33269202009-04-20 16:05:10 -07001085 return new UnsupportedOperationException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001086 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001087
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001088 if (code == ERROR_CODE_INVALID_RESPONSE) {
Fred Quintana33269202009-04-20 16:05:10 -07001089 return new AuthenticatorException(message);
Fred Quintanaa698f422009-04-08 19:14:54 -07001090 }
1091
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001092 if (code == ERROR_CODE_BAD_ARGUMENTS) {
Fred Quintana33269202009-04-20 16:05:10 -07001093 return new IllegalArgumentException(message);
1094 }
1095
1096 return new AuthenticatorException(message);
1097 }
1098
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001099 private class GetAuthTokenByTypeAndFeaturesTask
1100 extends AmsTask implements AccountManagerCallback<Bundle> {
Fred Quintana33269202009-04-20 16:05:10 -07001101 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
1102 final String[] features, Activity activityForPrompting,
1103 final Bundle addAccountOptions, final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001104 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001105 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 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001114 volatile AccountManagerFuture<Bundle> mFuture = null;
Fred Quintana33269202009-04-20 16:05:10 -07001115 final String mAccountType;
1116 final String mAuthTokenType;
1117 final String[] mFeatures;
1118 final Bundle mAddAccountOptions;
1119 final Bundle mLoginOptions;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001120 final AccountManagerCallback<Bundle> mMyCallback;
Fred Quintana33269202009-04-20 16:05:10 -07001121
1122 public void doWork() throws RemoteException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001123 getAccountsByTypeAndFeatures(mAccountType, mFeatures,
1124 new AccountManagerCallback<Account[]>() {
1125 public void run(AccountManagerFuture<Account[]> future) {
1126 Account[] accounts;
Fred Quintana33269202009-04-20 16:05:10 -07001127 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001128 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;
Fred Quintana33269202009-04-20 16:05:10 -07001138 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001139
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();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001149 result.putString(KEY_ACCOUNT_NAME, null);
1150 result.putString(KEY_ACCOUNT_TYPE, null);
1151 result.putString(KEY_AUTHTOKEN, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001152 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,
Fred Quintana33269202009-04-20 16:05:10 -07001167 mActivity, mMyCallback, mHandler);
1168 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001169 } 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(
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001175 value.getString(KEY_ACCOUNT_NAME),
1176 value.getString(KEY_ACCOUNT_TYPE));
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001177 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
1178 mActivity, mMyCallback, mHandler);
1179 }
Fred Quintana33269202009-04-20 16:05:10 -07001180
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001181 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");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001190 intent.putExtra(KEY_ACCOUNTS, accounts);
1191 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001192 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();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001198 result.putString(KEY_ACCOUNTS, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001199 try {
1200 mResponse.onResult(result);
1201 } catch (RemoteException e) {
1202 // this will never happen
1203 }
1204 // we are done
Fred Quintana33269202009-04-20 16:05:10 -07001205 }
Fred Quintana33269202009-04-20 16:05:10 -07001206 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001207 }}, mHandler);
Fred Quintana33269202009-04-20 16:05:10 -07001208 }
1209
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001210 public void run(AccountManagerFuture<Bundle> future) {
Fred Quintana33269202009-04-20 16:05:10 -07001211 try {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001212 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);
Fred Quintana33269202009-04-20 16:05:10 -07001219 }
1220 }
1221 }
1222
Fred Quintana756b7352009-10-21 13:43:10 -07001223 /**
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
Fred Quintana31957f12009-10-21 13:43:10 -07001250 * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
Fred Quintana756b7352009-10-21 13:43:10 -07001251 * @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 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001265 public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
Fred Quintana33269202009-04-20 16:05:10 -07001266 final String accountType, final String authTokenType, final String[] features,
1267 final Activity activityForPrompting, final Bundle addAccountOptions,
Fred Quintana31957f12009-10-21 13:43:10 -07001268 final Bundle getAuthTokenOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001269 final AccountManagerCallback<Bundle> callback, final Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001270 if (accountType == null) throw new IllegalArgumentException("account type is null");
1271 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001272 final GetAuthTokenByTypeAndFeaturesTask task =
1273 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
Fred Quintana31957f12009-10-21 13:43:10 -07001274 activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001275 task.start();
1276 return task;
Fred Quintana60307342009-03-24 22:48:12 -07001277 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001278
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001279 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Fred Quintanad9d2f112009-04-23 13:36:27 -07001280 Maps.newHashMap();
1281
Fred Quintanad9d2f112009-04-23 13:36:27 -07001282 /**
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) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001289 final Account[] accounts = getAccounts();
1290 // send the result to the listeners
1291 synchronized (mAccountsUpdatedListeners) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001292 for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001293 mAccountsUpdatedListeners.entrySet()) {
1294 postToHandler(entry.getValue(), entry.getKey(), accounts);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001295 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001296 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001297 }
1298 };
1299
1300 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001301 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001302 * 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.
Fred Quintanae00a3112009-09-22 15:13:30 -07001304 * <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.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001308 * @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 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001316 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
Fred Quintanad9d2f112009-04-23 13:36:27 -07001317 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();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001332 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
Costin Manolacheb6437242009-09-10 16:14:12 -07001333 // To recover from disk-full.
1334 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001335 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1336 }
1337 }
1338
1339 if (updateImmediately) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001340 postToHandler(handler, listener, getAccounts());
Fred Quintanad9d2f112009-04-23 13:36:27 -07001341 }
1342 }
1343
1344 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001345 * Remove an {@link OnAccountsUpdateListener} that was previously registered with
Fred Quintanad9d2f112009-04-23 13:36:27 -07001346 * {@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 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001351 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
Fred Quintanad9d2f112009-04-23 13:36:27 -07001352 if (listener == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001353 Log.e(TAG, "Missing listener");
1354 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001355 }
1356 synchronized (mAccountsUpdatedListeners) {
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001357 if (!mAccountsUpdatedListeners.containsKey(listener)) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001358 Log.e(TAG, "Listener was not previously added");
1359 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001360 }
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001361 mAccountsUpdatedListeners.remove(listener);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001362 if (mAccountsUpdatedListeners.isEmpty()) {
1363 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1364 }
1365 }
1366 }
Fred Quintana60307342009-03-24 22:48:12 -07001367}