blob: 4fcaa884b1c6060055249f4da01189bac9ba4bca [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;
Fred Quintanaa698f422009-04-08 19:14:54 -070024import android.os.Bundle;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.RemoteException;
Fred Quintana33269202009-04-20 16:05:10 -070028import android.os.Parcelable;
Fred Quintanad9d2f112009-04-23 13:36:27 -070029import android.util.Config;
30import 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 Quintana33269202009-04-20 16:05:10 -070045 * A class that helps with interactions with the AccountManagerService. It provides
Fred Quintana60307342009-03-24 22:48:12 -070046 * methods to allow for account, password, and authtoken management for all accounts on the
47 * device. Some of these calls are implemented with the help of the corresponding
48 * {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling:
Fred Quintanaa698f422009-04-08 19:14:54 -070049 * AccountManager accountManager = AccountManager.get(context);
Fred Quintana60307342009-03-24 22:48:12 -070050 *
51 * <p>
Fred Quintanaa698f422009-04-08 19:14:54 -070052 * TODO(fredq) this interface is still in flux
Fred Quintana60307342009-03-24 22:48:12 -070053 */
54public class AccountManager {
55 private static final String TAG = "AccountManager";
56
57 private final Context mContext;
58 private final IAccountManager mService;
Fred Quintanad9d2f112009-04-23 13:36:27 -070059 private final Handler mMainHandler;
Fred Quintana60307342009-03-24 22:48:12 -070060
Fred Quintana33269202009-04-20 16:05:10 -070061 /**
62 * @hide
63 */
Fred Quintana60307342009-03-24 22:48:12 -070064 public AccountManager(Context context, IAccountManager service) {
65 mContext = context;
66 mService = service;
Fred Quintanad9d2f112009-04-23 13:36:27 -070067 mMainHandler = new Handler(mContext.getMainLooper());
Fred Quintana60307342009-03-24 22:48:12 -070068 }
69
Fred Quintana0eabf022009-04-27 15:08:17 -070070 /**
71 * @hide used for testing only
72 */
73 public AccountManager(Context context, IAccountManager service, Handler handler) {
74 mContext = context;
75 mService = service;
76 mMainHandler = handler;
77 }
78
Fred Quintanaa698f422009-04-08 19:14:54 -070079 public static AccountManager get(Context context) {
80 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
81 }
82
83 public String blockingGetPassword(Account account) {
84 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -070085 try {
86 return mService.getPassword(account);
87 } catch (RemoteException e) {
88 // if this happens the entire runtime will restart
89 throw new RuntimeException(e);
90 }
91 }
92
Fred Quintanaa698f422009-04-08 19:14:54 -070093 public Future1<String> getPassword(final Future1Callback<String> callback,
94 final Account account, final Handler handler) {
95 return startAsFuture(callback, handler, new Callable<String>() {
96 public String call() throws Exception {
97 return blockingGetPassword(account);
98 }
99 });
100 }
101
102 public String blockingGetUserData(Account account, String key) {
103 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700104 try {
105 return mService.getUserData(account, key);
106 } catch (RemoteException e) {
107 // if this happens the entire runtime will restart
108 throw new RuntimeException(e);
109 }
110 }
111
Fred Quintanaa698f422009-04-08 19:14:54 -0700112 public Future1<String> getUserData(Future1Callback<String> callback,
113 final Account account, final String key, Handler handler) {
114 return startAsFuture(callback, handler, new Callable<String>() {
115 public String call() throws Exception {
116 return blockingGetUserData(account, key);
117 }
118 });
119 }
120
121 public String[] blockingGetAuthenticatorTypes() {
122 ensureNotOnMainThread();
123 try {
124 return mService.getAuthenticatorTypes();
125 } catch (RemoteException e) {
126 // if this happens the entire runtime will restart
127 throw new RuntimeException(e);
128 }
129 }
130
131 public Future1<String[]> getAuthenticatorTypes(Future1Callback<String[]> callback,
132 Handler handler) {
133 return startAsFuture(callback, handler, new Callable<String[]>() {
134 public String[] call() throws Exception {
135 return blockingGetAuthenticatorTypes();
136 }
137 });
138 }
139
Fred Quintana60307342009-03-24 22:48:12 -0700140 public Account[] blockingGetAccounts() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700141 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700142 try {
143 return mService.getAccounts();
144 } catch (RemoteException e) {
145 // if this happens the entire runtime will restart
146 throw new RuntimeException(e);
147 }
148 }
149
Fred Quintana60307342009-03-24 22:48:12 -0700150 public Account[] blockingGetAccountsByType(String accountType) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700151 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700152 try {
153 return mService.getAccountsByType(accountType);
154 } catch (RemoteException e) {
155 // if this happens the entire runtime will restart
156 throw new RuntimeException(e);
157 }
158 }
159
Fred Quintanaa698f422009-04-08 19:14:54 -0700160 public Future1<Account[]> getAccounts(Future1Callback<Account[]> callback, Handler handler) {
161 return startAsFuture(callback, handler, new Callable<Account[]>() {
162 public Account[] call() throws Exception {
163 return blockingGetAccounts();
164 }
165 });
166 }
167
168 public Future1<Account[]> getAccountsByType(Future1Callback<Account[]> callback,
169 final String type, Handler handler) {
170 return startAsFuture(callback, handler, new Callable<Account[]>() {
171 public Account[] call() throws Exception {
172 return blockingGetAccountsByType(type);
173 }
174 });
175 }
176
177 public boolean blockingAddAccountExplicitly(Account account, String password, Bundle extras) {
178 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700179 try {
180 return mService.addAccount(account, password, extras);
181 } catch (RemoteException e) {
182 // if this happens the entire runtime will restart
183 throw new RuntimeException(e);
184 }
185 }
186
Fred Quintanaa698f422009-04-08 19:14:54 -0700187 public Future1<Boolean> addAccountExplicitly(final Future1Callback<Boolean> callback,
188 final Account account, final String password, final Bundle extras,
189 final Handler handler) {
190 return startAsFuture(callback, handler, new Callable<Boolean>() {
191 public Boolean call() throws Exception {
192 return blockingAddAccountExplicitly(account, password, extras);
193 }
194 });
195 }
196
197 public void blockingRemoveAccount(Account account) {
198 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700199 try {
200 mService.removeAccount(account);
201 } catch (RemoteException e) {
202 // if this happens the entire runtime will restart
203 }
204 }
205
Fred Quintanaa698f422009-04-08 19:14:54 -0700206 public Future1<Void> removeAccount(Future1Callback<Void> callback, final Account account,
207 final Handler handler) {
208 return startAsFuture(callback, handler, new Callable<Void>() {
209 public Void call() throws Exception {
210 blockingRemoveAccount(account);
211 return null;
212 }
213 });
214 }
215
216 public void blockingInvalidateAuthToken(String accountType, String authToken) {
217 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700218 try {
219 mService.invalidateAuthToken(accountType, authToken);
220 } catch (RemoteException e) {
221 // if this happens the entire runtime will restart
222 }
223 }
224
Fred Quintanaa698f422009-04-08 19:14:54 -0700225 public Future1<Void> invalidateAuthToken(Future1Callback<Void> callback,
226 final String accountType, final String authToken, final Handler handler) {
227 return startAsFuture(callback, handler, new Callable<Void>() {
228 public Void call() throws Exception {
229 blockingInvalidateAuthToken(accountType, authToken);
230 return null;
231 }
232 });
233 }
234
235 public String blockingPeekAuthToken(Account account, String authTokenType) {
236 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700237 try {
238 return mService.peekAuthToken(account, authTokenType);
239 } catch (RemoteException e) {
240 // if this happens the entire runtime will restart
241 throw new RuntimeException(e);
242 }
243 }
244
Fred Quintanaa698f422009-04-08 19:14:54 -0700245 public Future1<String> peekAuthToken(Future1Callback<String> callback,
246 final Account account, final String authTokenType, final Handler handler) {
247 return startAsFuture(callback, handler, new Callable<String>() {
248 public String call() throws Exception {
249 return blockingPeekAuthToken(account, authTokenType);
250 }
251 });
252 }
253
254 public void blockingSetPassword(Account account, String password) {
255 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700256 try {
257 mService.setPassword(account, password);
258 } catch (RemoteException e) {
259 // if this happens the entire runtime will restart
260 }
261 }
262
Fred Quintanaa698f422009-04-08 19:14:54 -0700263 public Future1<Void> setPassword(Future1Callback<Void> callback,
264 final Account account, final String password, final Handler handler) {
265 return startAsFuture(callback, handler, new Callable<Void>() {
266 public Void call() throws Exception {
267 blockingSetPassword(account, password);
268 return null;
269 }
270 });
271 }
272
273 public void blockingClearPassword(Account account) {
274 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700275 try {
276 mService.clearPassword(account);
277 } catch (RemoteException e) {
278 // if this happens the entire runtime will restart
279 }
280 }
281
Fred Quintanaa698f422009-04-08 19:14:54 -0700282 public Future1<Void> clearPassword(final Future1Callback<Void> callback, final Account account,
283 final Handler handler) {
284 return startAsFuture(callback, handler, new Callable<Void>() {
285 public Void call() throws Exception {
286 blockingClearPassword(account);
287 return null;
288 }
289 });
290 }
291
292 public void blockingSetUserData(Account account, String key, String value) {
293 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700294 try {
295 mService.setUserData(account, key, value);
296 } catch (RemoteException e) {
297 // if this happens the entire runtime will restart
298 }
299 }
300
Fred Quintanaa698f422009-04-08 19:14:54 -0700301 public Future1<Void> setUserData(Future1Callback<Void> callback,
302 final Account account, final String key, final String value, final Handler handler) {
303 return startAsFuture(callback, handler, new Callable<Void>() {
304 public Void call() throws Exception {
305 blockingSetUserData(account, key, value);
306 return null;
307 }
308 });
Fred Quintana60307342009-03-24 22:48:12 -0700309 }
310
Fred Quintanaa698f422009-04-08 19:14:54 -0700311 public void blockingSetAuthToken(Account account, String authTokenType, String authToken) {
312 ensureNotOnMainThread();
Fred Quintana60307342009-03-24 22:48:12 -0700313 try {
314 mService.setAuthToken(account, authTokenType, authToken);
315 } catch (RemoteException e) {
316 // if this happens the entire runtime will restart
317 }
318 }
319
Fred Quintanaa698f422009-04-08 19:14:54 -0700320 public Future1<Void> setAuthToken(Future1Callback<Void> callback,
321 final Account account, final String authTokenType, final String authToken,
322 final Handler handler) {
323 return startAsFuture(callback, handler, new Callable<Void>() {
324 public Void call() throws Exception {
325 blockingSetAuthToken(account, authTokenType, authToken);
326 return null;
327 }
328 });
329 }
330
331 public String blockingGetAuthToken(Account account, String authTokenType,
332 boolean notifyAuthFailure)
333 throws OperationCanceledException, IOException, AuthenticatorException {
334 ensureNotOnMainThread();
335 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
336 null /* handler */).getResult();
337 return bundle.getString(Constants.AUTHTOKEN_KEY);
338 }
339
340 /**
341 * Request the auth token for this account/authTokenType. If this succeeds then the
342 * auth token will then be passed to the activity. If this results in an authentication
343 * failure then a login intent will be returned that can be invoked to prompt the user to
344 * update their credentials. This login activity will return the auth token to the calling
345 * activity. If activity is null then the login intent will not be invoked.
346 *
347 * @param account the account whose auth token should be retrieved
348 * @param authTokenType the auth token type that should be retrieved
349 * @param loginOptions
350 * @param activity the activity to launch the login intent, if necessary, and to which
351 */
352 public Future2 getAuthToken(
353 final Account account, final String authTokenType, final Bundle loginOptions,
354 final Activity activity, Future2Callback callback, Handler handler) {
355 if (activity == null) throw new IllegalArgumentException("activity is null");
356 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
357 return new AmsTask(activity, handler, callback) {
358 public void doWork() throws RemoteException {
359 mService.getAuthToken(mResponse, account, authTokenType,
360 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
361 loginOptions);
362 }
Fred Quintana33269202009-04-20 16:05:10 -0700363 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700364 }
365
366 public Future2 getAuthToken(
367 final Account account, final String authTokenType, final boolean notifyAuthFailure,
368 Future2Callback callback, Handler handler) {
369 if (account == null) throw new IllegalArgumentException("account is null");
370 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
371 return new AmsTask(null, handler, callback) {
372 public void doWork() throws RemoteException {
373 mService.getAuthToken(mResponse, account, authTokenType,
374 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
375 }
Fred Quintana33269202009-04-20 16:05:10 -0700376 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700377 }
378
379 public Future2 addAccount(final String accountType,
Fred Quintana33269202009-04-20 16:05:10 -0700380 final String authTokenType, final String[] requiredFeatures,
381 final Bundle addAccountOptions,
Fred Quintanaa698f422009-04-08 19:14:54 -0700382 final Activity activity, Future2Callback callback, Handler handler) {
383 return new AmsTask(activity, handler, callback) {
384 public void doWork() throws RemoteException {
385 mService.addAcount(mResponse, accountType, authTokenType,
Fred Quintana33269202009-04-20 16:05:10 -0700386 requiredFeatures, activity != null, addAccountOptions);
Fred Quintanaa698f422009-04-08 19:14:54 -0700387 }
Fred Quintana33269202009-04-20 16:05:10 -0700388 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700389 }
390
391 /** @deprecated use {@link #confirmCredentials} instead */
392 public Future1<Boolean> confirmPassword(final Account account, final String password,
393 Future1Callback<Boolean> callback, Handler handler) {
394 return new AMSTaskBoolean(handler, callback) {
395 public void doWork() throws RemoteException {
396 mService.confirmPassword(response, account, password);
397 }
398 };
399 }
400
Fred Quintana33269202009-04-20 16:05:10 -0700401 public Account[] blockingGetAccountsWithTypeAndFeatures(String type, String[] features)
402 throws AuthenticatorException, IOException, OperationCanceledException {
403 Future2 future = getAccountsWithTypeAndFeatures(type, features,
404 null /* callback */, null /* handler */);
405 Bundle result = future.getResult();
406 Parcelable[] accountsTemp = result.getParcelableArray(Constants.ACCOUNTS_KEY);
407 if (accountsTemp == null) {
408 throw new AuthenticatorException("accounts should not be null");
409 }
410 Account[] accounts = new Account[accountsTemp.length];
411 for (int i = 0; i < accountsTemp.length; i++) {
412 accounts[i] = (Account) accountsTemp[i];
413 }
414 return accounts;
415 }
416
417 public Future2 getAccountsWithTypeAndFeatures(
418 final String type, final String[] features,
419 Future2Callback callback, Handler handler) {
420 if (type == null) throw new IllegalArgumentException("type is null");
421 return new AmsTask(null /* activity */, handler, callback) {
422 public void doWork() throws RemoteException {
423 mService.getAccountsByTypeAndFeatures(mResponse, type, features);
424 }
425 }.start();
426 }
427
Fred Quintanaa698f422009-04-08 19:14:54 -0700428 public Future2 confirmCredentials(final Account account, final Activity activity,
429 final Future2Callback callback,
430 final Handler handler) {
431 return new AmsTask(activity, handler, callback) {
432 public void doWork() throws RemoteException {
433 mService.confirmCredentials(mResponse, account, activity != null);
434 }
Fred Quintana33269202009-04-20 16:05:10 -0700435 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700436 }
437
438 public Future2 updateCredentials(final Account account, final String authTokenType,
439 final Bundle loginOptions, final Activity activity,
440 final Future2Callback callback,
441 final Handler handler) {
442 return new AmsTask(activity, handler, callback) {
443 public void doWork() throws RemoteException {
444 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
445 loginOptions);
446 }
Fred Quintana33269202009-04-20 16:05:10 -0700447 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700448 }
449
450 public Future2 editProperties(final String accountType, final Activity activity,
451 final Future2Callback callback,
452 final Handler handler) {
453 return new AmsTask(activity, handler, callback) {
454 public void doWork() throws RemoteException {
455 mService.editProperties(mResponse, accountType, activity != null);
456 }
Fred Quintana33269202009-04-20 16:05:10 -0700457 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700458 }
459
460 private void ensureNotOnMainThread() {
461 final Looper looper = Looper.myLooper();
462 if (looper != null && looper == mContext.getMainLooper()) {
463 // We really want to throw an exception here, but GTalkService exercises this
464 // path quite a bit and needs some serious rewrite in order to work properly.
465 //noinspection ThrowableInstanceNeverThrow
466// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
467// new Exception());
468 // TODO(fredq) remove the log and throw this exception when the callers are fixed
469// throw new IllegalStateException(
470// "calling this from your main thread can lead to deadlock");
Fred Quintana60307342009-03-24 22:48:12 -0700471 }
472 }
473
Fred Quintanaa698f422009-04-08 19:14:54 -0700474 private void postToHandler(Handler handler, final Future2Callback callback,
475 final Future2 future) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700476 handler = handler == null ? mMainHandler : handler;
477 handler.post(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700478 public void run() {
479 callback.run(future);
480 }
481 });
482 }
Fred Quintana60307342009-03-24 22:48:12 -0700483
Fred Quintanad9d2f112009-04-23 13:36:27 -0700484 private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener,
485 final Account[] accounts) {
486 handler = handler == null ? mMainHandler : handler;
487 handler.post(new Runnable() {
488 public void run() {
489 listener.onAccountsUpdated(accounts);
490 }
491 });
492 }
493
Fred Quintanaa698f422009-04-08 19:14:54 -0700494 private <V> void postToHandler(Handler handler, final Future1Callback<V> callback,
495 final Future1<V> future) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700496 handler = handler == null ? mMainHandler : handler;
497 handler.post(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700498 public void run() {
499 callback.run(future);
500 }
501 });
502 }
Fred Quintana60307342009-03-24 22:48:12 -0700503
Fred Quintanaa698f422009-04-08 19:14:54 -0700504 private <V> Future1<V> startAsFuture(Future1Callback<V> callback, Handler handler,
505 Callable<V> callable) {
506 final FutureTaskWithCallback<V> task =
507 new FutureTaskWithCallback<V>(callback, callable, handler);
508 new Thread(task).start();
509 return task;
510 }
Fred Quintana60307342009-03-24 22:48:12 -0700511
Fred Quintanaa698f422009-04-08 19:14:54 -0700512 private class FutureTaskWithCallback<V> extends FutureTask<V> implements Future1<V> {
513 final Future1Callback<V> mCallback;
514 final Handler mHandler;
Fred Quintana60307342009-03-24 22:48:12 -0700515
Fred Quintanaa698f422009-04-08 19:14:54 -0700516 public FutureTaskWithCallback(Future1Callback<V> callback, Callable<V> callable,
517 Handler handler) {
518 super(callable);
519 mCallback = callback;
520 mHandler = handler;
521 }
522
523 protected void done() {
524 if (mCallback != null) {
525 postToHandler(mHandler, mCallback, this);
Fred Quintana60307342009-03-24 22:48:12 -0700526 }
527 }
Fred Quintana60307342009-03-24 22:48:12 -0700528
Fred Quintanaa698f422009-04-08 19:14:54 -0700529 public V internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException {
530 try {
531 if (timeout == null) {
532 return get();
533 } else {
534 return get(timeout, unit);
535 }
536 } catch (InterruptedException e) {
537 // we will cancel the task below
538 } catch (CancellationException e) {
539 // we will cancel the task below
540 } catch (TimeoutException e) {
541 // we will cancel the task below
542 } catch (ExecutionException e) {
543 // this should never happen
544 throw new IllegalStateException(e.getCause());
545 } finally {
546 cancel(true /* interruptIfRunning */);
547 }
548 throw new OperationCanceledException();
549 }
550
551 public V getResult() throws OperationCanceledException {
552 return internalGetResult(null, null);
553 }
554
555 public V getResult(long timeout, TimeUnit unit) throws OperationCanceledException {
556 return internalGetResult(null, null);
Fred Quintana60307342009-03-24 22:48:12 -0700557 }
558 }
559
Fred Quintana33269202009-04-20 16:05:10 -0700560 private abstract class AmsTask extends FutureTask<Bundle> implements Future2 {
Fred Quintanaa698f422009-04-08 19:14:54 -0700561 final IAccountManagerResponse mResponse;
562 final Handler mHandler;
563 final Future2Callback mCallback;
564 final Activity mActivity;
Fred Quintana33269202009-04-20 16:05:10 -0700565 final Thread mThread;
Fred Quintanaa698f422009-04-08 19:14:54 -0700566 public AmsTask(Activity activity, Handler handler, Future2Callback callback) {
567 super(new Callable<Bundle>() {
568 public Bundle call() throws Exception {
569 throw new IllegalStateException("this should never be called");
570 }
571 });
572
573 mHandler = handler;
574 mCallback = callback;
575 mActivity = activity;
576 mResponse = new Response();
Fred Quintana33269202009-04-20 16:05:10 -0700577 mThread = new Thread(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700578 public void run() {
579 try {
580 doWork();
581 } catch (RemoteException e) {
582 // never happens
583 }
584 }
Fred Quintana33269202009-04-20 16:05:10 -0700585 }, "AmsTask");
586 }
587
588 public final Future2 start() {
589 mThread.start();
590 return this;
Fred Quintana60307342009-03-24 22:48:12 -0700591 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700592
593 public abstract void doWork() throws RemoteException;
594
595 private Bundle internalGetResult(Long timeout, TimeUnit unit)
596 throws OperationCanceledException, IOException, AuthenticatorException {
597 try {
598 if (timeout == null) {
599 return get();
600 } else {
601 return get(timeout, unit);
602 }
603 } catch (CancellationException e) {
604 throw new OperationCanceledException();
605 } catch (TimeoutException e) {
606 // fall through and cancel
607 } catch (InterruptedException e) {
608 // fall through and cancel
609 } catch (ExecutionException e) {
610 final Throwable cause = e.getCause();
611 if (cause instanceof IOException) {
612 throw (IOException) cause;
613 } else if (cause instanceof UnsupportedOperationException) {
614 throw new AuthenticatorException(cause);
615 } else if (cause instanceof AuthenticatorException) {
616 throw (AuthenticatorException) cause;
617 } else if (cause instanceof RuntimeException) {
618 throw (RuntimeException) cause;
619 } else if (cause instanceof Error) {
620 throw (Error) cause;
621 } else {
622 throw new IllegalStateException(cause);
623 }
624 } finally {
625 cancel(true /* interrupt if running */);
626 }
627 throw new OperationCanceledException();
628 }
629
630 public Bundle getResult()
631 throws OperationCanceledException, IOException, AuthenticatorException {
632 return internalGetResult(null, null);
633 }
634
635 public Bundle getResult(long timeout, TimeUnit unit)
636 throws OperationCanceledException, IOException, AuthenticatorException {
637 return internalGetResult(timeout, unit);
638 }
639
640 protected void done() {
641 if (mCallback != null) {
642 postToHandler(mHandler, mCallback, this);
643 }
644 }
645
646 /** Handles the responses from the AccountManager */
647 private class Response extends IAccountManagerResponse.Stub {
648 public void onResult(Bundle bundle) {
649 Intent intent = bundle.getParcelable("intent");
650 if (intent != null && mActivity != null) {
651 // since the user provided an Activity we will silently start intents
652 // that we see
653 mActivity.startActivity(intent);
654 // leave the Future running to wait for the real response to this request
655 } else {
656 set(bundle);
657 }
658 }
659
660 public void onError(int code, String message) {
661 if (code == Constants.ERROR_CODE_CANCELED) {
662 // the authenticator indicated that this request was canceled, do so now
663 cancel(true /* mayInterruptIfRunning */);
664 return;
665 }
666 setException(convertErrorToException(code, message));
667 }
668 }
669
Fred Quintana60307342009-03-24 22:48:12 -0700670 }
671
Fred Quintana33269202009-04-20 16:05:10 -0700672 private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> {
Fred Quintanaa698f422009-04-08 19:14:54 -0700673 final IAccountManagerResponse response;
674 final Handler mHandler;
675 final Future1Callback<Boolean> mCallback;
676 public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) {
677 super(new Callable<Boolean>() {
678 public Boolean call() throws Exception {
679 throw new IllegalStateException("this should never be called");
680 }
681 });
682
683 mHandler = handler;
684 mCallback = callback;
685 response = new Response();
686
687 new Thread(new Runnable() {
688 public void run() {
689 try {
690 doWork();
691 } catch (RemoteException e) {
692 // never happens
693 }
694 }
695 }).start();
Fred Quintana60307342009-03-24 22:48:12 -0700696 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700697
698 public abstract void doWork() throws RemoteException;
699
700
701 protected void done() {
702 if (mCallback != null) {
703 postToHandler(mHandler, mCallback, this);
704 }
705 }
706
707 private Boolean internalGetResult(Long timeout, TimeUnit unit) {
708 try {
709 if (timeout == null) {
710 return get();
711 } else {
712 return get(timeout, unit);
713 }
714 } catch (InterruptedException e) {
715 // fall through and cancel
716 } catch (TimeoutException e) {
717 // fall through and cancel
718 } catch (CancellationException e) {
719 return false;
720 } catch (ExecutionException e) {
721 final Throwable cause = e.getCause();
722 if (cause instanceof IOException) {
723 return false;
724 } else if (cause instanceof UnsupportedOperationException) {
725 return false;
726 } else if (cause instanceof AuthenticatorException) {
727 return false;
728 } else if (cause instanceof RuntimeException) {
729 throw (RuntimeException) cause;
730 } else if (cause instanceof Error) {
731 throw (Error) cause;
732 } else {
733 throw new IllegalStateException(cause);
734 }
735 } finally {
736 cancel(true /* interrupt if running */);
737 }
738 return false;
739 }
740
741 public Boolean getResult() throws OperationCanceledException {
742 return internalGetResult(null, null);
743 }
744
745 public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException {
746 return internalGetResult(timeout, unit);
747 }
748
749 private class Response extends IAccountManagerResponse.Stub {
750 public void onResult(Bundle bundle) {
751 try {
752 if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) {
753 set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY));
754 return;
755 }
756 } catch (ClassCastException e) {
757 // we will set the exception below
758 }
759 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response");
760 }
761
762 public void onError(int code, String message) {
763 if (code == Constants.ERROR_CODE_CANCELED) {
764 cancel(true /* mayInterruptIfRunning */);
765 return;
766 }
767 setException(convertErrorToException(code, message));
768 }
769 }
770
Fred Quintana60307342009-03-24 22:48:12 -0700771 }
772
Fred Quintanaa698f422009-04-08 19:14:54 -0700773 private Exception convertErrorToException(int code, String message) {
774 if (code == Constants.ERROR_CODE_NETWORK_ERROR) {
775 return new IOException(message);
Fred Quintana60307342009-03-24 22:48:12 -0700776 }
Fred Quintana60307342009-03-24 22:48:12 -0700777
Fred Quintanaa698f422009-04-08 19:14:54 -0700778 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) {
Fred Quintana33269202009-04-20 16:05:10 -0700779 return new UnsupportedOperationException(message);
Fred Quintana60307342009-03-24 22:48:12 -0700780 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700781
782 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) {
Fred Quintana33269202009-04-20 16:05:10 -0700783 return new AuthenticatorException(message);
Fred Quintanaa698f422009-04-08 19:14:54 -0700784 }
785
Fred Quintana33269202009-04-20 16:05:10 -0700786 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) {
787 return new IllegalArgumentException(message);
788 }
789
790 return new AuthenticatorException(message);
791 }
792
793 private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback {
794 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
795 final String[] features, Activity activityForPrompting,
796 final Bundle addAccountOptions, final Bundle loginOptions,
797 Future2Callback callback, Handler handler) {
798 super(activityForPrompting, handler, callback);
799 if (accountType == null) throw new IllegalArgumentException("account type is null");
800 mAccountType = accountType;
801 mAuthTokenType = authTokenType;
802 mFeatures = features;
803 mAddAccountOptions = addAccountOptions;
804 mLoginOptions = loginOptions;
805 mMyCallback = this;
806 }
807 volatile Future2 mFuture = null;
808 final String mAccountType;
809 final String mAuthTokenType;
810 final String[] mFeatures;
811 final Bundle mAddAccountOptions;
812 final Bundle mLoginOptions;
813 final Future2Callback mMyCallback;
814
815 public void doWork() throws RemoteException {
816 getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() {
817 public void run(Future2 future) {
818 Bundle getAccountsResult;
819 try {
820 getAccountsResult = future.getResult();
821 } catch (OperationCanceledException e) {
822 setException(e);
823 return;
824 } catch (IOException e) {
825 setException(e);
826 return;
827 } catch (AuthenticatorException e) {
828 setException(e);
829 return;
830 }
831
832 Parcelable[] accounts =
833 getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY);
834 if (accounts.length == 0) {
835 if (mActivity != null) {
836 // no accounts, add one now. pretend that the user directly
837 // made this request
838 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
839 mAddAccountOptions, mActivity, mMyCallback, mHandler);
840 } else {
841 // send result since we can't prompt to add an account
842 Bundle result = new Bundle();
843 result.putString(Constants.ACCOUNT_NAME_KEY, null);
844 result.putString(Constants.ACCOUNT_TYPE_KEY, null);
845 result.putString(Constants.AUTHTOKEN_KEY, null);
846 try {
847 mResponse.onResult(result);
848 } catch (RemoteException e) {
849 // this will never happen
850 }
851 // we are done
852 }
853 } else if (accounts.length == 1) {
854 // have a single account, return an authtoken for it
855 if (mActivity == null) {
856 mFuture = getAuthToken((Account) accounts[0], mAuthTokenType,
857 false /* notifyAuthFailure */, mMyCallback, mHandler);
858 } else {
859 mFuture = getAuthToken((Account) accounts[0],
860 mAuthTokenType, mLoginOptions,
861 mActivity, mMyCallback, mHandler);
862 }
863 } else {
864 if (mActivity != null) {
865 IAccountManagerResponse chooseResponse =
866 new IAccountManagerResponse.Stub() {
867 public void onResult(Bundle value) throws RemoteException {
868 Account account = new Account(
869 value.getString(Constants.ACCOUNT_NAME_KEY),
870 value.getString(Constants.ACCOUNT_TYPE_KEY));
871 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
872 mActivity, mMyCallback, mHandler);
873 }
874
875 public void onError(int errorCode, String errorMessage)
876 throws RemoteException {
877 mResponse.onError(errorCode, errorMessage);
878 }
879 };
880 // have many accounts, launch the chooser
881 Intent intent = new Intent();
882 intent.setClassName("android",
883 "android.accounts.ChooseAccountActivity");
884 intent.putExtra(Constants.ACCOUNTS_KEY, accounts);
885 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY,
886 new AccountManagerResponse(chooseResponse));
887 mActivity.startActivity(intent);
888 // the result will arrive via the IAccountManagerResponse
889 } else {
890 // send result since we can't prompt to select an account
891 Bundle result = new Bundle();
892 result.putString(Constants.ACCOUNTS_KEY, null);
893 try {
894 mResponse.onResult(result);
895 } catch (RemoteException e) {
896 // this will never happen
897 }
898 // we are done
899 }
900 }
901 }}, mHandler);
902 }
903
904
905
906 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying
907 // future that we create. We need to do things like have cancel cancel the mFuture, if set
908 // or to cause this to be canceled if mFuture isn't set.
909 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2.
910
911 public void run(Future2 future) {
912 try {
913 set(future.get());
914 } catch (InterruptedException e) {
915 cancel(true);
916 } catch (CancellationException e) {
917 cancel(true);
918 } catch (ExecutionException e) {
919 setException(e.getCause());
920 }
921 }
922 }
923
924 public void getAuthTokenByFeatures(
925 final String accountType, final String authTokenType, final String[] features,
926 final Activity activityForPrompting, final Bundle addAccountOptions,
927 final Bundle loginOptions,
928 final Future2Callback callback, final Handler handler) {
929 if (accountType == null) throw new IllegalArgumentException("account type is null");
930 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
931 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
932 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start();
Fred Quintana60307342009-03-24 22:48:12 -0700933 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700934
935 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners =
936 Maps.newHashMap();
937
938 // These variable are only used from the LOGIN_ACCOUNTS_CHANGED_ACTION BroadcastReceiver
939 // and its getAccounts() callback which are both invoked only on the main thread. As a
940 // result we don't need to protect against concurrent accesses and any changes are guaranteed
941 // to be visible when used. Basically, these two variables are thread-confined.
942 private Future1<Account[]> mAccountsLookupFuture = null;
943 private boolean mAccountLookupPending = false;
944
945 /**
946 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
947 * so that it can read the updated list of accounts and send them to the listener
948 * in mAccountsUpdatedListeners.
949 */
950 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
951 public void onReceive(final Context context, final Intent intent) {
952 if (mAccountsLookupFuture != null) {
953 // an accounts lookup is already in progress,
954 // don't bother starting another request
955 mAccountLookupPending = true;
956 return;
957 }
958 // initiate a read of the accounts
959 mAccountsLookupFuture = getAccounts(new Future1Callback<Account[]>() {
960 public void run(Future1<Account[]> future) {
961 // clear the future so that future receives will try the lookup again
962 mAccountsLookupFuture = null;
963
964 // get the accounts array
965 Account[] accounts;
966 try {
967 accounts = future.getResult();
968 } catch (OperationCanceledException e) {
969 // this should never happen, but if it does pretend we got another
970 // accounts changed broadcast
971 if (Config.LOGD) {
972 Log.d(TAG, "the accounts lookup for listener notifications was "
973 + "canceled, try again by simulating the receipt of "
974 + "a LOGIN_ACCOUNTS_CHANGED_ACTION broadcast");
975 }
976 onReceive(context, intent);
977 return;
978 }
979
980 // send the result to the listeners
981 synchronized (mAccountsUpdatedListeners) {
982 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry :
983 mAccountsUpdatedListeners.entrySet()) {
984 Account[] accountsCopy = new Account[accounts.length];
985 // send the listeners a copy to make sure that one doesn't
986 // change what another sees
987 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
988 postToHandler(entry.getValue(), entry.getKey(), accountsCopy);
989 }
990 }
991
992 // If mAccountLookupPending was set when the account lookup finished it
993 // means that we had previously ignored a LOGIN_ACCOUNTS_CHANGED_ACTION
994 // intent because a lookup was already in progress. Now that we are done
995 // with this lookup and notification pretend that another intent
996 // was received by calling onReceive() directly.
997 if (mAccountLookupPending) {
998 mAccountLookupPending = false;
999 onReceive(context, intent);
1000 return;
1001 }
1002 }
1003 }, mMainHandler);
1004 }
1005 };
1006
1007 /**
1008 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}.
1009 * The listener is guaranteed to be invoked on the thread of the Handler that is passed
1010 * in or the main thread's Handler if handler is null.
1011 * @param listener the listener to add
1012 * @param handler the Handler whose thread will be used to invoke the listener. If null
1013 * the AccountManager context's main thread will be used.
1014 * @param updateImmediately if true then the listener will be invoked as a result of this
1015 * call.
1016 * @throws IllegalArgumentException if listener is null
1017 * @throws IllegalStateException if listener was already added
1018 */
1019 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener,
1020 Handler handler, boolean updateImmediately) {
1021 if (listener == null) {
1022 throw new IllegalArgumentException("the listener is null");
1023 }
1024 synchronized (mAccountsUpdatedListeners) {
1025 if (mAccountsUpdatedListeners.containsKey(listener)) {
1026 throw new IllegalStateException("this listener is already added");
1027 }
1028 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
1029
1030 mAccountsUpdatedListeners.put(listener, handler);
1031
1032 if (wasEmpty) {
1033 // Register a broadcast receiver to monitor account changes
1034 IntentFilter intentFilter = new IntentFilter();
1035 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
1036 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
1037 }
1038 }
1039
1040 if (updateImmediately) {
1041 getAccounts(new Future1Callback<Account[]>() {
1042 public void run(Future1<Account[]> future) {
1043 try {
1044 listener.onAccountsUpdated(future.getResult());
1045 } catch (OperationCanceledException e) {
1046 // ignore
1047 }
1048 }
1049 }, handler);
1050 }
1051 }
1052
1053 /**
1054 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with
1055 * {@link #addOnAccountsUpdatedListener}.
1056 * @param listener the listener to remove
1057 * @throws IllegalArgumentException if listener is null
1058 * @throws IllegalStateException if listener was not already added
1059 */
1060 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) {
1061 if (listener == null) {
1062 throw new IllegalArgumentException("the listener is null");
1063 }
1064 synchronized (mAccountsUpdatedListeners) {
1065 if (mAccountsUpdatedListeners.remove(listener) == null) {
1066 throw new IllegalStateException("this listener was not previously added");
1067 }
1068 if (mAccountsUpdatedListeners.isEmpty()) {
1069 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
1070 }
1071 }
1072 }
Fred Quintana60307342009-03-24 22:48:12 -07001073}