blob: 19e741ab55ff9bb693010bf74606951b4391c75c [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 /**
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800233 * Tests that the given account has the specified features. If this account does not exist
234 * then this call returns false.
235 * <p>
236 * This call returns immediately but runs asynchronously and the result is accessed via the
237 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
238 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
239 * method asynchronously then they will generally pass in a callback object that will get
240 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
241 * they will generally pass null for the callback and instead call
242 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
243 * which will then block until the request completes.
244 * <p>
245 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
246 *
247 * @param account The {@link Account} to test
248 * @param features the features for which to test
249 * @param callback A callback to invoke when the request completes. If null then
250 * no callback is invoked.
251 * @param handler The {@link Handler} to use to invoke the callback. If null then the
252 * main thread's {@link Handler} is used.
253 * @return an {@link AccountManagerFuture} that represents the future result of the call.
254 * The future result is a {@link Boolean} that is true if the account exists and has the
255 * specified features.
256 */
Fred Quintana3084a6f2010-01-14 18:02:03 -0800257 public AccountManagerFuture<Boolean> hasFeatures(final Account account,
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800258 final String[] features,
259 AccountManagerCallback<Boolean> callback, Handler handler) {
260 return new Future2Task<Boolean>(handler, callback) {
261 public void doWork() throws RemoteException {
Fred Quintana3084a6f2010-01-14 18:02:03 -0800262 mService.hasFeatures(mResponse, account, features);
Fred Quintanabb68a4f2010-01-13 17:28:39 -0800263 }
264 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
265 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
266 throw new AuthenticatorException("no result in response");
267 }
268 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
269 }
270 }.start();
271 }
272
273 /**
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800274 * Add an account to the AccountManager's set of known accounts.
Fred Quintana756b7352009-10-21 13:43:10 -0700275 * <p>
276 * Requires that the caller has permission
277 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
278 * with the same UID as the Authenticator for the account.
279 * @param account The account to add
280 * @param password The password to associate with the account. May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700281 * @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 -0700282 * @return true if the account was sucessfully added, false otherwise, for example,
283 * if the account already exists or if the account is null
284 */
Fred Quintana31957f12009-10-21 13:43:10 -0700285 public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
Fred Quintana60307342009-03-24 22:48:12 -0700286 try {
Fred Quintana31957f12009-10-21 13:43:10 -0700287 return mService.addAccount(account, password, userdata);
Fred Quintana60307342009-03-24 22:48:12 -0700288 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700289 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700290 throw new RuntimeException(e);
291 }
292 }
293
Fred Quintana756b7352009-10-21 13:43:10 -0700294 /**
295 * Removes the given account. If this account does not exist then this call has no effect.
296 * <p>
297 * This call returns immediately but runs asynchronously and the result is accessed via the
298 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
299 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
300 * method asynchronously then they will generally pass in a callback object that will get
301 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
302 * they will generally pass null for the callback and instead call
303 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
304 * which will then block until the request completes.
305 * <p>
306 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
307 *
308 * @param account The {@link Account} to remove
309 * @param callback A callback to invoke when the request completes. If null then
310 * no callback is invoked.
311 * @param handler The {@link Handler} to use to invoke the callback. If null then the
312 * main thread's {@link Handler} is used.
313 * @return an {@link AccountManagerFuture} that represents the future result of the call.
314 * The future result is a {@link Boolean} that is true if the account is successfully removed
315 * or false if the authenticator refuses to remove the account.
316 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700317 public AccountManagerFuture<Boolean> removeAccount(final Account account,
318 AccountManagerCallback<Boolean> callback, Handler handler) {
319 return new Future2Task<Boolean>(handler, callback) {
320 public void doWork() throws RemoteException {
321 mService.removeAccount(mResponse, account);
Fred Quintanaa698f422009-04-08 19:14:54 -0700322 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700323 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700324 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700325 throw new AuthenticatorException("no result in response");
326 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700327 return bundle.getBoolean(KEY_BOOLEAN_RESULT);
Fred Quintanaa698f422009-04-08 19:14:54 -0700328 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700329 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700330 }
331
Fred Quintana756b7352009-10-21 13:43:10 -0700332 /**
333 * Removes the given authtoken. If this authtoken does not exist for the given account type
334 * then this call has no effect.
335 * <p>
336 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
337 * @param accountType the account type of the authtoken to invalidate
338 * @param authToken the authtoken to invalidate
339 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700340 public void invalidateAuthToken(final String accountType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700341 try {
342 mService.invalidateAuthToken(accountType, authToken);
343 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700344 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700345 throw new RuntimeException(e);
346 }
347 }
348
Fred Quintana756b7352009-10-21 13:43:10 -0700349 /**
350 * Gets the authtoken named by "authTokenType" for the specified account if it is cached
351 * by the AccountManager. If no authtoken is cached then null is returned rather than
352 * asking the authenticaticor to generate one. If the account or the
353 * authtoken do not exist then null is returned.
354 * <p>
355 * Requires that the caller has permission
356 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
357 * with the same UID as the Authenticator for the account.
358 * @param account the account whose authtoken is to be retrieved, must not be null
359 * @param authTokenType the type of authtoken to retrieve
360 * @return an authtoken for the given account and authTokenType, if one is cached by the
361 * AccountManager, null otherwise.
362 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700363 public String peekAuthToken(final Account account, final String authTokenType) {
Fred Quintana31957f12009-10-21 13:43:10 -0700364 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700365 Log.e(TAG, "peekAuthToken: the account must not be null");
366 return null;
Fred Quintana31957f12009-10-21 13:43:10 -0700367 }
368 if (authTokenType == null) {
369 return null;
370 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700371 try {
372 return mService.peekAuthToken(account, authTokenType);
373 } catch (RemoteException e) {
374 // won't ever happen
375 throw new RuntimeException(e);
376 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700377 }
378
Fred Quintana756b7352009-10-21 13:43:10 -0700379 /**
380 * Sets the password for the account. The password may be null. If the account does not exist
381 * then this call has no affect.
382 * <p>
383 * Requires that the caller has permission
384 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
385 * with the same UID as the Authenticator for the account.
386 * @param account the account whose password is to be set. Must not be null.
387 * @param password the password to set for the account. May be null.
388 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700389 public void setPassword(final Account account, final String password) {
Fred Quintana31957f12009-10-21 13:43:10 -0700390 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700391 Log.e(TAG, "the account must not be null");
392 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700393 }
Fred Quintana60307342009-03-24 22:48:12 -0700394 try {
395 mService.setPassword(account, password);
396 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700397 // won't ever happen
398 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700399 }
400 }
401
Fred Quintana756b7352009-10-21 13:43:10 -0700402 /**
403 * Sets the password for account to null. If the account does not exist then this call
404 * has no effect.
405 * <p>
406 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
407 * @param account the account whose password is to be cleared. Must not be null.
408 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700409 public void clearPassword(final Account account) {
Fred Quintana31957f12009-10-21 13:43:10 -0700410 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700411 Log.e(TAG, "the account must not be null");
412 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700413 }
Fred Quintana60307342009-03-24 22:48:12 -0700414 try {
415 mService.clearPassword(account);
416 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700417 // won't ever happen
418 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700419 }
420 }
421
Fred Quintana756b7352009-10-21 13:43:10 -0700422 /**
423 * Sets account's userdata named "key" to the specified value. If the account does not
424 * exist then this call has no effect.
425 * <p>
426 * Requires that the caller has permission
427 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
428 * with the same UID as the Authenticator for the account.
429 * @param account the account whose userdata is to be set. Must not be null.
430 * @param key the key of the userdata to set. Must not be null.
431 * @param value the value to set. May be null.
432 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700433 public void setUserData(final Account account, final String key, final String value) {
Fred Quintana31957f12009-10-21 13:43:10 -0700434 if (account == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700435 Log.e(TAG, "the account must not be null");
436 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700437 }
438 if (key == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -0700439 Log.e(TAG, "the key must not be null");
440 return;
Fred Quintana31957f12009-10-21 13:43:10 -0700441 }
Fred Quintana60307342009-03-24 22:48:12 -0700442 try {
443 mService.setUserData(account, key, value);
444 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700445 // won't ever happen
446 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700447 }
448 }
449
Fred Quintana756b7352009-10-21 13:43:10 -0700450 /**
451 * Sets the authtoken named by "authTokenType" to the value specified by authToken.
452 * If the account does not exist then this call has no effect.
453 * <p>
454 * Requires that the caller has permission
455 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
456 * with the same UID as the Authenticator for the account.
457 * @param account the account whose authtoken is to be set. Must not be null.
458 * @param authTokenType the type of the authtoken to set. Must not be null.
459 * @param authToken the authToken to set. May be null.
460 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700461 public void setAuthToken(Account account, final String authTokenType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700462 try {
463 mService.setAuthToken(account, authTokenType, authToken);
464 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700465 // won't ever happen
466 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700467 }
468 }
469
Fred Quintana756b7352009-10-21 13:43:10 -0700470 /**
471 * Convenience method that makes a blocking call to
472 * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
473 * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
474 * <p>
475 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
476 * @param account the account whose authtoken is to be retrieved, must not be null
477 * @param authTokenType the type of authtoken to retrieve
478 * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
479 * for the account if no authtoken is cached by the AccountManager and the the authenticator
480 * does not have valid credentials to get an authtoken.
481 * @return an authtoken for the given account and authTokenType, if one is cached by the
482 * AccountManager, null otherwise.
483 * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
484 * an invalid response.
485 * @throws OperationCanceledException if the request is canceled for any reason
486 * @throws java.io.IOException if the authenticator experiences an IOException while attempting
487 * to communicate with its backend server.
488 */
Fred Quintanaa698f422009-04-08 19:14:54 -0700489 public String blockingGetAuthToken(Account account, String authTokenType,
490 boolean notifyAuthFailure)
491 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaa698f422009-04-08 19:14:54 -0700492 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
493 null /* handler */).getResult();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700494 return bundle.getString(KEY_AUTHTOKEN);
Fred Quintanaa698f422009-04-08 19:14:54 -0700495 }
496
497 /**
Fred Quintana756b7352009-10-21 13:43:10 -0700498 * Request that an authtoken of the specified type be returned for an account.
499 * If the Account Manager has a cached authtoken of the requested type then it will
500 * service the request itself. Otherwise it will pass the request on to the authenticator.
501 * The authenticator can try to service this request with information it already has stored
502 * in the AccountManager but may need to launch an activity to prompt the
503 * user to enter credentials. If it is able to retrieve the authtoken it will be returned
504 * in the result.
505 * <p>
506 * If the authenticator needs to prompt the user for credentials it will return an intent to
507 * the activity that will do the prompting. If an activity is supplied then that activity
508 * will be used to launch the intent and the result will come from it. Otherwise a result will
509 * be returned that contains the intent.
510 * <p>
511 * This call returns immediately but runs asynchronously and the result is accessed via the
512 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
513 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
514 * method asynchronously then they will generally pass in a callback object that will get
515 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
516 * they will generally pass null for the callback and instead call
517 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
518 * which will then block until the request completes.
519 * <p>
520 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
Fred Quintanaa698f422009-04-08 19:14:54 -0700521 *
Fred Quintana756b7352009-10-21 13:43:10 -0700522 * @param account The account whose credentials are to be updated.
523 * @param authTokenType the auth token to retrieve as part of updating the credentials.
524 * May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700525 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700526 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
527 * the intent will be started with this activity. If activity is null then the result will
528 * be returned as-is.
529 * @param callback A callback to invoke when the request completes. If null then
530 * no callback is invoked.
531 * @param handler The {@link Handler} to use to invoke the callback. If null then the
532 * main thread's {@link Handler} is used.
533 * @return an {@link AccountManagerFuture} that represents the future result of the call.
534 * The future result is a {@link Bundle} that contains:
535 * <ul>
536 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
537 * </ul>
538 * If the user presses "back" then the request will be canceled.
Fred Quintanaa698f422009-04-08 19:14:54 -0700539 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700540 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintana31957f12009-10-21 13:43:10 -0700541 final Account account, final String authTokenType, final Bundle options,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700542 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700543 if (activity == null) throw new IllegalArgumentException("activity is null");
544 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
545 return new AmsTask(activity, handler, callback) {
546 public void doWork() throws RemoteException {
547 mService.getAuthToken(mResponse, account, authTokenType,
548 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
Fred Quintana31957f12009-10-21 13:43:10 -0700549 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700550 }
Fred Quintana33269202009-04-20 16:05:10 -0700551 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700552 }
553
Fred Quintana756b7352009-10-21 13:43:10 -0700554 /**
555 * Request that an authtoken of the specified type be returned for an account.
556 * If the Account Manager has a cached authtoken of the requested type then it will
557 * service the request itself. Otherwise it will pass the request on to the authenticator.
558 * The authenticator can try to service this request with information it already has stored
559 * in the AccountManager but may need to launch an activity to prompt the
560 * user to enter credentials. If it is able to retrieve the authtoken it will be returned
561 * in the result.
562 * <p>
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800563 * If the authenticator needs to prompt the user for credentials, rather than returning the
564 * authtoken it will instead return an intent for
Fred Quintana756b7352009-10-21 13:43:10 -0700565 * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800566 * is true then a notification will be created that launches this intent. This intent can be
567 * invoked by the caller directly to start the activity that prompts the user for the
568 * updated credentials. Otherwise this activity will not be run until the user activates
569 * the notification.
Fred Quintana756b7352009-10-21 13:43:10 -0700570 * <p>
571 * This call returns immediately but runs asynchronously and the result is accessed via the
572 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
573 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
574 * method asynchronously then they will generally pass in a callback object that will get
575 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
576 * they will generally pass null for the callback and instead call
577 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
578 * which will then block until the request completes.
579 * <p>
580 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
581 *
582 * @param account The account whose credentials are to be updated.
583 * @param authTokenType the auth token to retrieve as part of updating the credentials.
584 * May be null.
585 * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
586 * result then a "sign-on needed" notification will be created that will launch this intent.
587 * @param callback A callback to invoke when the request completes. If null then
588 * no callback is invoked.
589 * @param handler The {@link Handler} to use to invoke the callback. If null then the
590 * main thread's {@link Handler} is used.
591 * @return an {@link AccountManagerFuture} that represents the future result of the call.
592 * The future result is a {@link Bundle} that contains either:
593 * <ul>
594 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
595 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
596 * if the authenticator is able to retrieve the auth token
597 * </ul>
598 * If the user presses "back" then the request will be canceled.
599 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700600 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintanaa698f422009-04-08 19:14:54 -0700601 final Account account, final String authTokenType, final boolean notifyAuthFailure,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700602 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700603 if (account == null) throw new IllegalArgumentException("account is null");
604 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
605 return new AmsTask(null, handler, callback) {
606 public void doWork() throws RemoteException {
607 mService.getAuthToken(mResponse, account, authTokenType,
608 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
609 }
Fred Quintana33269202009-04-20 16:05:10 -0700610 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700611 }
612
Fred Quintana756b7352009-10-21 13:43:10 -0700613 /**
614 * Request that an account be added with the given accountType. This request
615 * is processed by the authenticator for the account type. If no authenticator is registered
616 * in the system then {@link AuthenticatorException} is thrown.
617 * <p>
618 * This call returns immediately but runs asynchronously and the result is accessed via the
619 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
620 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
621 * method asynchronously then they will generally pass in a callback object that will get
622 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
623 * they will generally pass null for the callback and instead call
624 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
625 * which will then block until the request completes.
626 * <p>
627 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
628 *
629 * @param accountType The type of account to add. This must not be null.
630 * @param authTokenType The account that is added should be able to service this auth token
631 * type. This may be null.
632 * @param requiredFeatures The account that is added should support these features.
633 * This array may be null or empty.
634 * @param addAccountOptions A bundle of authenticator-specific options that is passed on
635 * to the authenticator. This may be null.
636 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
637 * the intent will be started with this activity. If activity is null then the result will
638 * be returned as-is.
639 * @param callback A callback to invoke when the request completes. If null then
640 * no callback is invoked.
641 * @param handler The {@link Handler} to use to invoke the callback. If null then the
642 * main thread's {@link Handler} is used.
643 * @return an {@link AccountManagerFuture} that represents the future result of the call.
644 * The future result is a {@link Bundle} that contains either:
645 * <ul>
646 * <li> {@link #KEY_INTENT}, or
647 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
648 * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
649 * </ul>
650 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700651 public AccountManagerFuture<Bundle> addAccount(final String accountType,
Fred Quintana33269202009-04-20 16:05:10 -0700652 final String authTokenType, final String[] requiredFeatures,
653 final Bundle addAccountOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700654 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700655 return new AmsTask(activity, handler, callback) {
656 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700657 if (accountType == null) {
658 Log.e(TAG, "the account must not be null");
659 // to unblock caller waiting on Future.get()
Fred Quintanac5d1c6d2010-01-27 12:17:49 -0800660 set(new Bundle());
Costin Manolache88a211b2009-10-29 11:30:11 -0700661 return;
662 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700663 mService.addAcount(mResponse, accountType, authTokenType,
Fred Quintana33269202009-04-20 16:05:10 -0700664 requiredFeatures, activity != null, addAccountOptions);
Fred Quintanaa698f422009-04-08 19:14:54 -0700665 }
Fred Quintana33269202009-04-20 16:05:10 -0700666 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700667 }
668
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700669 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
670 final String type, final String[] features,
671 AccountManagerCallback<Account[]> callback, Handler handler) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700672 return new Future2Task<Account[]>(handler, callback) {
673 public void doWork() throws RemoteException {
Costin Manolache88a211b2009-10-29 11:30:11 -0700674 if (type == null) {
675 Log.e(TAG, "Type is null");
676 set(new Account[0]);
677 return;
678 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700679 mService.getAccountsByFeatures(mResponse, type, features);
680 }
681 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700682 if (!bundle.containsKey(KEY_ACCOUNTS)) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700683 throw new AuthenticatorException("no result in response");
684 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700685 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700686 Account[] descs = new Account[parcelables.length];
687 for (int i = 0; i < parcelables.length; i++) {
688 descs[i] = (Account) parcelables[i];
689 }
690 return descs;
691 }
692 }.start();
693 }
694
Fred Quintana756b7352009-10-21 13:43:10 -0700695 /**
696 * Requests that the authenticator checks that the user knows the credentials for the account.
697 * This is typically done by returning an intent to an activity that prompts the user to
698 * enter the credentials. This request
699 * is processed by the authenticator for the account. If no matching authenticator is
700 * registered in the system then {@link AuthenticatorException} is thrown.
701 * <p>
702 * This call returns immediately but runs asynchronously and the result is accessed via the
703 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
704 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
705 * method asynchronously then they will generally pass in a callback object that will get
706 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
707 * they will generally pass null for the callback and instead call
708 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
709 * which will then block until the request completes.
710 * <p>
711 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
712 *
713 * @param account The account whose credentials are to be checked
714 * @param options authenticator specific options for the request
715 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
716 * the intent will be started with this activity. If activity is null then the result will
717 * be returned as-is.
718 * @param callback A callback to invoke when the request completes. If null then
719 * no callback is invoked.
720 * @param handler The {@link Handler} to use to invoke the callback. If null then the
721 * main thread's {@link Handler} is used.
722 * @return an {@link AccountManagerFuture} that represents the future result of the call.
723 * The future result is a {@link Bundle} that contains either:
724 * <ul>
725 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
726 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
727 * credentials
728 * </ul>
729 * If the user presses "back" then the request will be canceled.
730 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700731 public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
732 final Bundle options,
733 final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700734 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700735 final Handler handler) {
736 return new AmsTask(activity, handler, callback) {
737 public void doWork() throws RemoteException {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700738 mService.confirmCredentials(mResponse, account, options, activity != null);
Fred Quintanaa698f422009-04-08 19:14:54 -0700739 }
Fred Quintana33269202009-04-20 16:05:10 -0700740 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700741 }
742
Fred Quintana756b7352009-10-21 13:43:10 -0700743 /**
744 * Requests that the authenticator update the the credentials for a user. This is typically
745 * done by returning an intent to an activity that will prompt the user to update the stored
746 * credentials for the account. This request
747 * is processed by the authenticator for the account. If no matching authenticator is
748 * registered in the system then {@link AuthenticatorException} is thrown.
749 * <p>
750 * This call returns immediately but runs asynchronously and the result is accessed via the
751 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
752 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
753 * method asynchronously then they will generally pass in a callback object that will get
754 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
755 * they will generally pass null for the callback and instead call
756 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
757 * which will then block until the request completes.
758 * <p>
759 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
760 *
761 * @param account The account whose credentials are to be updated.
762 * @param authTokenType the auth token to retrieve as part of updating the credentials.
763 * May be null.
Fred Quintana31957f12009-10-21 13:43:10 -0700764 * @param options authenticator specific options for the request
Fred Quintana756b7352009-10-21 13:43:10 -0700765 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
766 * the intent will be started with this activity. If activity is null then the result will
767 * be returned as-is.
768 * @param callback A callback to invoke when the request completes. If null then
769 * no callback is invoked.
770 * @param handler The {@link Handler} to use to invoke the callback. If null then the
771 * main thread's {@link Handler} is used.
772 * @return an {@link AccountManagerFuture} that represents the future result of the call.
773 * The future result is a {@link Bundle} that contains either:
774 * <ul>
775 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
776 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
777 * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided.
778 * </ul>
779 * If the user presses "back" then the request will be canceled.
780 */
781 public AccountManagerFuture<Bundle> updateCredentials(final Account account,
782 final String authTokenType,
Fred Quintana31957f12009-10-21 13:43:10 -0700783 final Bundle options, final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700784 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700785 final Handler handler) {
786 return new AmsTask(activity, handler, callback) {
787 public void doWork() throws RemoteException {
788 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
Fred Quintana31957f12009-10-21 13:43:10 -0700789 options);
Fred Quintanaa698f422009-04-08 19:14:54 -0700790 }
Fred Quintana33269202009-04-20 16:05:10 -0700791 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700792 }
793
Fred Quintana756b7352009-10-21 13:43:10 -0700794 /**
795 * Request that the properties for an authenticator be updated. This is typically done by
796 * returning an intent to an activity that will allow the user to make changes. This request
797 * is processed by the authenticator for the account. If no matching authenticator is
798 * registered in the system then {@link AuthenticatorException} is thrown.
799 * <p>
800 * This call returns immediately but runs asynchronously and the result is accessed via the
801 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
802 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
803 * method asynchronously then they will generally pass in a callback object that will get
804 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
805 * they will generally pass null for the callback and instead call
806 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
807 * which will then block until the request completes.
808 * <p>
809 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
810 *
811 * @param accountType The account type of the authenticator whose properties are to be edited.
812 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
813 * the intent will be started with this activity. If activity is null then the result will
814 * be returned as-is.
815 * @param callback A callback to invoke when the request completes. If null then
816 * no callback is invoked.
817 * @param handler The {@link Handler} to use to invoke the callback. If null then the
818 * main thread's {@link Handler} is used.
819 * @return an {@link AccountManagerFuture} that represents the future result of the call.
820 * The future result is a {@link Bundle} that contains either:
821 * <ul>
822 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
823 * <li> nothing, returned if the edit completes successfully
824 * </ul>
825 * If the user presses "back" then the request will be canceled.
826 */
827 public AccountManagerFuture<Bundle> editProperties(final String accountType,
828 final Activity activity, final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700829 final Handler handler) {
830 return new AmsTask(activity, handler, callback) {
831 public void doWork() throws RemoteException {
832 mService.editProperties(mResponse, accountType, activity != null);
833 }
Fred Quintana33269202009-04-20 16:05:10 -0700834 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700835 }
836
837 private void ensureNotOnMainThread() {
838 final Looper looper = Looper.myLooper();
839 if (looper != null && looper == mContext.getMainLooper()) {
840 // We really want to throw an exception here, but GTalkService exercises this
841 // path quite a bit and needs some serious rewrite in order to work properly.
842 //noinspection ThrowableInstanceNeverThrow
843// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
844// new Exception());
Costin Manolache88a211b2009-10-29 11:30:11 -0700845 // TODO remove the log and throw this exception when the callers are fixed
Fred Quintanaa698f422009-04-08 19:14:54 -0700846// throw new IllegalStateException(
847// "calling this from your main thread can lead to deadlock");
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 Quintanaffd0cb042009-08-15 21:45:26 -0700912 ensureNotOnMainThread();
Fred Quintanaa698f422009-04-08 19:14:54 -0700913 try {
914 if (timeout == null) {
915 return get();
916 } else {
917 return get(timeout, unit);
918 }
919 } catch (CancellationException e) {
920 throw new OperationCanceledException();
921 } catch (TimeoutException e) {
922 // fall through and cancel
923 } catch (InterruptedException e) {
924 // fall through and cancel
925 } catch (ExecutionException e) {
926 final Throwable cause = e.getCause();
927 if (cause instanceof IOException) {
928 throw (IOException) cause;
929 } else if (cause instanceof UnsupportedOperationException) {
930 throw new AuthenticatorException(cause);
931 } else if (cause instanceof AuthenticatorException) {
932 throw (AuthenticatorException) cause;
933 } else if (cause instanceof RuntimeException) {
934 throw (RuntimeException) cause;
935 } else if (cause instanceof Error) {
936 throw (Error) cause;
937 } else {
938 throw new IllegalStateException(cause);
939 }
940 } finally {
941 cancel(true /* interrupt if running */);
942 }
943 throw new OperationCanceledException();
944 }
945
946 public Bundle getResult()
947 throws OperationCanceledException, IOException, AuthenticatorException {
948 return internalGetResult(null, null);
949 }
950
951 public Bundle getResult(long timeout, TimeUnit unit)
952 throws OperationCanceledException, IOException, AuthenticatorException {
953 return internalGetResult(timeout, unit);
954 }
955
956 protected void done() {
957 if (mCallback != null) {
958 postToHandler(mHandler, mCallback, this);
959 }
960 }
961
962 /** Handles the responses from the AccountManager */
963 private class Response extends IAccountManagerResponse.Stub {
964 public void onResult(Bundle bundle) {
965 Intent intent = bundle.getParcelable("intent");
966 if (intent != null && mActivity != null) {
967 // since the user provided an Activity we will silently start intents
968 // that we see
969 mActivity.startActivity(intent);
970 // leave the Future running to wait for the real response to this request
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700971 } else if (bundle.getBoolean("retry")) {
972 try {
973 doWork();
974 } catch (RemoteException e) {
975 // this will only happen if the system process is dead, which means
976 // we will be dying ourselves
977 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700978 } else {
979 set(bundle);
980 }
981 }
982
983 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -0700984 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700985 // the authenticator indicated that this request was canceled, do so now
986 cancel(true /* mayInterruptIfRunning */);
987 return;
988 }
989 setException(convertErrorToException(code, message));
990 }
991 }
992
Fred Quintana60307342009-03-24 22:48:12 -0700993 }
994
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700995 private abstract class BaseFutureTask<T> extends FutureTask<T> {
996 final public IAccountManagerResponse mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700997 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700998
999 public BaseFutureTask(Handler handler) {
1000 super(new Callable<T>() {
1001 public T call() throws Exception {
Fred Quintanaa698f422009-04-08 19:14:54 -07001002 throw new IllegalStateException("this should never be called");
1003 }
1004 });
Fred Quintanaa698f422009-04-08 19:14:54 -07001005 mHandler = handler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001006 mResponse = new Response();
Fred Quintana60307342009-03-24 22:48:12 -07001007 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001008
1009 public abstract void doWork() throws RemoteException;
1010
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001011 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
Fred Quintanaa698f422009-04-08 19:14:54 -07001012
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001013 protected void postRunnableToHandler(Runnable runnable) {
1014 Handler handler = (mHandler == null) ? mMainHandler : mHandler;
1015 handler.post(runnable);
Fred Quintanaa698f422009-04-08 19:14:54 -07001016 }
1017
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001018 protected void startTask() {
Fred Quintanaa698f422009-04-08 19:14:54 -07001019 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001020 doWork();
1021 } catch (RemoteException e) {
1022 setException(e);
Fred Quintanaa698f422009-04-08 19:14:54 -07001023 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001024 }
1025
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001026 protected class Response extends IAccountManagerResponse.Stub {
Fred Quintanaa698f422009-04-08 19:14:54 -07001027 public void onResult(Bundle bundle) {
1028 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001029 T result = bundleToResult(bundle);
1030 if (result == null) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001031 return;
1032 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001033 set(result);
1034 return;
Fred Quintanaa698f422009-04-08 19:14:54 -07001035 } catch (ClassCastException e) {
1036 // we will set the exception below
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001037 } catch (AuthenticatorException e) {
1038 // we will set the exception below
Fred Quintanaa698f422009-04-08 19:14:54 -07001039 }
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001040 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
Fred Quintanaa698f422009-04-08 19:14:54 -07001041 }
1042
1043 public void onError(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001044 if (code == ERROR_CODE_CANCELED) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001045 cancel(true /* mayInterruptIfRunning */);
1046 return;
1047 }
1048 setException(convertErrorToException(code, message));
1049 }
1050 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001051 }
1052
1053 private abstract class Future2Task<T>
1054 extends BaseFutureTask<T> implements AccountManagerFuture<T> {
1055 final AccountManagerCallback<T> mCallback;
1056 public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
1057 super(handler);
1058 mCallback = callback;
1059 }
1060
1061 protected void done() {
1062 if (mCallback != null) {
1063 postRunnableToHandler(new Runnable() {
1064 public void run() {
1065 mCallback.run(Future2Task.this);
1066 }
1067 });
1068 }
1069 }
1070
1071 public Future2Task<T> start() {
1072 startTask();
1073 return this;
1074 }
1075
1076 private T internalGetResult(Long timeout, TimeUnit unit)
1077 throws OperationCanceledException, IOException, AuthenticatorException {
1078 ensureNotOnMainThread();
1079 try {
1080 if (timeout == null) {
1081 return get();
1082 } else {
1083 return get(timeout, unit);
1084 }
1085 } catch (InterruptedException e) {
1086 // fall through and cancel
1087 } catch (TimeoutException e) {
1088 // fall through and cancel
1089 } catch (CancellationException e) {
1090 // fall through and cancel
1091 } catch (ExecutionException e) {
1092 final Throwable cause = e.getCause();
1093 if (cause instanceof IOException) {
1094 throw (IOException) cause;
1095 } else if (cause instanceof UnsupportedOperationException) {
1096 throw new AuthenticatorException(cause);
1097 } else if (cause instanceof AuthenticatorException) {
1098 throw (AuthenticatorException) cause;
1099 } else if (cause instanceof RuntimeException) {
1100 throw (RuntimeException) cause;
1101 } else if (cause instanceof Error) {
1102 throw (Error) cause;
1103 } else {
1104 throw new IllegalStateException(cause);
1105 }
1106 } finally {
1107 cancel(true /* interrupt if running */);
1108 }
1109 throw new OperationCanceledException();
1110 }
1111
1112 public T getResult()
1113 throws OperationCanceledException, IOException, AuthenticatorException {
1114 return internalGetResult(null, null);
1115 }
1116
1117 public T getResult(long timeout, TimeUnit unit)
1118 throws OperationCanceledException, IOException, AuthenticatorException {
1119 return internalGetResult(timeout, unit);
1120 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001121
Fred Quintana60307342009-03-24 22:48:12 -07001122 }
1123
Fred Quintanaa698f422009-04-08 19:14:54 -07001124 private Exception convertErrorToException(int code, String message) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001125 if (code == ERROR_CODE_NETWORK_ERROR) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001126 return new IOException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001127 }
Fred Quintana60307342009-03-24 22:48:12 -07001128
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001129 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
Fred Quintana33269202009-04-20 16:05:10 -07001130 return new UnsupportedOperationException(message);
Fred Quintana60307342009-03-24 22:48:12 -07001131 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001132
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001133 if (code == ERROR_CODE_INVALID_RESPONSE) {
Fred Quintana33269202009-04-20 16:05:10 -07001134 return new AuthenticatorException(message);
Fred Quintanaa698f422009-04-08 19:14:54 -07001135 }
1136
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001137 if (code == ERROR_CODE_BAD_ARGUMENTS) {
Fred Quintana33269202009-04-20 16:05:10 -07001138 return new IllegalArgumentException(message);
1139 }
1140
1141 return new AuthenticatorException(message);
1142 }
1143
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001144 private class GetAuthTokenByTypeAndFeaturesTask
1145 extends AmsTask implements AccountManagerCallback<Bundle> {
Fred Quintana33269202009-04-20 16:05:10 -07001146 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
1147 final String[] features, Activity activityForPrompting,
1148 final Bundle addAccountOptions, final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001149 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001150 super(activityForPrompting, handler, callback);
1151 if (accountType == null) throw new IllegalArgumentException("account type is null");
1152 mAccountType = accountType;
1153 mAuthTokenType = authTokenType;
1154 mFeatures = features;
1155 mAddAccountOptions = addAccountOptions;
1156 mLoginOptions = loginOptions;
1157 mMyCallback = this;
1158 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001159 volatile AccountManagerFuture<Bundle> mFuture = null;
Fred Quintana33269202009-04-20 16:05:10 -07001160 final String mAccountType;
1161 final String mAuthTokenType;
1162 final String[] mFeatures;
1163 final Bundle mAddAccountOptions;
1164 final Bundle mLoginOptions;
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001165 final AccountManagerCallback<Bundle> mMyCallback;
Fred Quintana33269202009-04-20 16:05:10 -07001166
1167 public void doWork() throws RemoteException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001168 getAccountsByTypeAndFeatures(mAccountType, mFeatures,
1169 new AccountManagerCallback<Account[]>() {
1170 public void run(AccountManagerFuture<Account[]> future) {
1171 Account[] accounts;
Fred Quintana33269202009-04-20 16:05:10 -07001172 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001173 accounts = future.getResult();
1174 } catch (OperationCanceledException e) {
1175 setException(e);
1176 return;
1177 } catch (IOException e) {
1178 setException(e);
1179 return;
1180 } catch (AuthenticatorException e) {
1181 setException(e);
1182 return;
Fred Quintana33269202009-04-20 16:05:10 -07001183 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001184
1185 if (accounts.length == 0) {
1186 if (mActivity != null) {
1187 // no accounts, add one now. pretend that the user directly
1188 // made this request
1189 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
1190 mAddAccountOptions, mActivity, mMyCallback, mHandler);
1191 } else {
1192 // send result since we can't prompt to add an account
1193 Bundle result = new Bundle();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001194 result.putString(KEY_ACCOUNT_NAME, null);
1195 result.putString(KEY_ACCOUNT_TYPE, null);
1196 result.putString(KEY_AUTHTOKEN, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001197 try {
1198 mResponse.onResult(result);
1199 } catch (RemoteException e) {
1200 // this will never happen
1201 }
1202 // we are done
1203 }
1204 } else if (accounts.length == 1) {
1205 // have a single account, return an authtoken for it
1206 if (mActivity == null) {
1207 mFuture = getAuthToken(accounts[0], mAuthTokenType,
1208 false /* notifyAuthFailure */, mMyCallback, mHandler);
1209 } else {
1210 mFuture = getAuthToken(accounts[0],
1211 mAuthTokenType, mLoginOptions,
Fred Quintana33269202009-04-20 16:05:10 -07001212 mActivity, mMyCallback, mHandler);
1213 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001214 } else {
1215 if (mActivity != null) {
1216 IAccountManagerResponse chooseResponse =
1217 new IAccountManagerResponse.Stub() {
1218 public void onResult(Bundle value) throws RemoteException {
1219 Account account = new Account(
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001220 value.getString(KEY_ACCOUNT_NAME),
1221 value.getString(KEY_ACCOUNT_TYPE));
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001222 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
1223 mActivity, mMyCallback, mHandler);
1224 }
Fred Quintana33269202009-04-20 16:05:10 -07001225
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001226 public void onError(int errorCode, String errorMessage)
1227 throws RemoteException {
1228 mResponse.onError(errorCode, errorMessage);
1229 }
1230 };
1231 // have many accounts, launch the chooser
1232 Intent intent = new Intent();
1233 intent.setClassName("android",
1234 "android.accounts.ChooseAccountActivity");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001235 intent.putExtra(KEY_ACCOUNTS, accounts);
1236 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001237 new AccountManagerResponse(chooseResponse));
1238 mActivity.startActivity(intent);
1239 // the result will arrive via the IAccountManagerResponse
1240 } else {
1241 // send result since we can't prompt to select an account
1242 Bundle result = new Bundle();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001243 result.putString(KEY_ACCOUNTS, null);
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001244 try {
1245 mResponse.onResult(result);
1246 } catch (RemoteException e) {
1247 // this will never happen
1248 }
1249 // we are done
Fred Quintana33269202009-04-20 16:05:10 -07001250 }
Fred Quintana33269202009-04-20 16:05:10 -07001251 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001252 }}, mHandler);
Fred Quintana33269202009-04-20 16:05:10 -07001253 }
1254
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001255 public void run(AccountManagerFuture<Bundle> future) {
Fred Quintana33269202009-04-20 16:05:10 -07001256 try {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001257 set(future.getResult());
1258 } catch (OperationCanceledException e) {
1259 cancel(true /* mayInterruptIfRUnning */);
1260 } catch (IOException e) {
1261 setException(e);
1262 } catch (AuthenticatorException e) {
1263 setException(e);
Fred Quintana33269202009-04-20 16:05:10 -07001264 }
1265 }
1266 }
1267
Fred Quintana756b7352009-10-21 13:43:10 -07001268 /**
1269 * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
1270 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
1271 * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
1272 * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
1273 * feature set, and addAccountOptions. If there is exactly one then
1274 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
1275 * called with that account. If there are more than one then a chooser activity is launched
1276 * to prompt the user to select one of them and then the authtoken is retrieved for it,
1277 * <p>
1278 * This call returns immediately but runs asynchronously and the result is accessed via the
1279 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
1280 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
1281 * method asynchronously then they will generally pass in a callback object that will get
1282 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
1283 * they will generally pass null for the callback and instead call
1284 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
1285 * which will then block until the request completes.
1286 * <p>
1287 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
1288 *
1289 * @param accountType the accountType to query; this must be non-null
1290 * @param authTokenType the type of authtoken to retrieve; this must be non-null
1291 * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
1292 * @param activityForPrompting The activity used to start any account management
1293 * activities that are required to fulfill this request. This may be null.
1294 * @param addAccountOptions authenticator-specific options used if an account needs to be added
Fred Quintana31957f12009-10-21 13:43:10 -07001295 * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
Fred Quintana756b7352009-10-21 13:43:10 -07001296 * @param callback A callback to invoke when the request completes. If null then
1297 * no callback is invoked.
1298 * @param handler The {@link Handler} to use to invoke the callback. If null then the
1299 * main thread's {@link Handler} is used.
1300 * @return an {@link AccountManagerFuture} that represents the future result of the call.
1301 * The future result is a {@link Bundle} that contains either:
1302 * <ul>
1303 * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
1304 * fulfill the request.
1305 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
1306 * request completes successfully.
1307 * </ul>
1308 * If the user presses "back" then the request will be canceled.
1309 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001310 public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
Fred Quintana33269202009-04-20 16:05:10 -07001311 final String accountType, final String authTokenType, final String[] features,
1312 final Activity activityForPrompting, final Bundle addAccountOptions,
Fred Quintana31957f12009-10-21 13:43:10 -07001313 final Bundle getAuthTokenOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001314 final AccountManagerCallback<Bundle> callback, final Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -07001315 if (accountType == null) throw new IllegalArgumentException("account type is null");
1316 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001317 final GetAuthTokenByTypeAndFeaturesTask task =
1318 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
Fred Quintana31957f12009-10-21 13:43:10 -07001319 activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001320 task.start();
1321 return task;
Fred Quintana60307342009-03-24 22:48:12 -07001322 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001323
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001324 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
Fred Quintanad9d2f112009-04-23 13:36:27 -07001325 Maps.newHashMap();
1326
Fred Quintanad9d2f112009-04-23 13:36:27 -07001327 /**
1328 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
1329 * so that it can read the updated list of accounts and send them to the listener
1330 * in mAccountsUpdatedListeners.
1331 */
1332 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
1333 public void onReceive(final Context context, final Intent intent) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001334 final Account[] accounts = getAccounts();
1335 // send the result to the listeners
1336 synchronized (mAccountsUpdatedListeners) {
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001337 for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001338 mAccountsUpdatedListeners.entrySet()) {
1339 postToHandler(entry.getValue(), entry.getKey(), accounts);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001340 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001341 }
Fred Quintanad9d2f112009-04-23 13:36:27 -07001342 }
1343 };
1344
1345 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001346 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001347 * The listener is guaranteed to be invoked on the thread of the Handler that is passed
1348 * in or the main thread's Handler if handler is null.
Fred Quintanae00a3112009-09-22 15:13:30 -07001349 * <p>
1350 * You must remove this listener before the context that was used to retrieve this
1351 * {@link AccountManager} instance goes away. This generally means when the Activity
1352 * or Service you are running is stopped.
Fred Quintanad9d2f112009-04-23 13:36:27 -07001353 * @param listener the listener to add
1354 * @param handler the Handler whose thread will be used to invoke the listener. If null
1355 * the AccountManager context's main thread will be used.
1356 * @param updateImmediately if true then the listener will be invoked as a result of this
1357 * call.
1358 * @throws IllegalArgumentException if listener is null
1359 * @throws IllegalStateException if listener was already added
1360 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001361 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
Fred Quintanad9d2f112009-04-23 13:36:27 -07001362 Handler handler, boolean updateImmediately) {
1363 if (listener == null) {
1364 throw new IllegalArgumentException("the listener is null");
1365 }
1366 synchronized (mAccountsUpdatedListeners) {
1367 if (mAccountsUpdatedListeners.containsKey(listener)) {
1368 throw new IllegalStateException("this listener is already added");
1369 }
1370 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
1371
1372 mAccountsUpdatedListeners.put(listener, handler);
1373
1374 if (wasEmpty) {
1375 // Register a broadcast receiver to monitor account changes
1376 IntentFilter intentFilter = new IntentFilter();
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001377 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
Costin Manolacheb6437242009-09-10 16:14:12 -07001378 // To recover from disk-full.
Fred Quintanac5d1c6d2010-01-27 12:17:49 -08001379 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001380 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1381 }
1382 }
1383
1384 if (updateImmediately) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001385 postToHandler(handler, listener, getAccounts());
Fred Quintanad9d2f112009-04-23 13:36:27 -07001386 }
1387 }
1388
1389 /**
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001390 * Remove an {@link OnAccountsUpdateListener} that was previously registered with
Fred Quintanad9d2f112009-04-23 13:36:27 -07001391 * {@link #addOnAccountsUpdatedListener}.
1392 * @param listener the listener to remove
1393 * @throws IllegalArgumentException if listener is null
1394 * @throws IllegalStateException if listener was not already added
1395 */
Fred Quintanaf7ae77c2009-10-02 17:19:31 -07001396 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
Fred Quintanad9d2f112009-04-23 13:36:27 -07001397 if (listener == null) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001398 Log.e(TAG, "Missing listener");
1399 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001400 }
1401 synchronized (mAccountsUpdatedListeners) {
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001402 if (!mAccountsUpdatedListeners.containsKey(listener)) {
Costin Manolache88a211b2009-10-29 11:30:11 -07001403 Log.e(TAG, "Listener was not previously added");
1404 return;
Fred Quintanad9d2f112009-04-23 13:36:27 -07001405 }
Bryan Mawhinney5be61f52009-09-24 14:50:25 +01001406 mAccountsUpdatedListeners.remove(listener);
Fred Quintanad9d2f112009-04-23 13:36:27 -07001407 if (mAccountsUpdatedListeners.isEmpty()) {
1408 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1409 }
1410 }
1411 }
Fred Quintana60307342009-03-24 22:48:12 -07001412}