blob: 1ee7f60fb9ce81545d82d10d164bee99e1412cc6 [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 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
Fred Quintanaffd0cb042009-08-15 21:45:26 -070083 public String getPassword(final Account account) {
Fred Quintana60307342009-03-24 22:48:12 -070084 try {
85 return mService.getPassword(account);
86 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -070087 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -070088 throw new RuntimeException(e);
89 }
90 }
91
Fred Quintanaffd0cb042009-08-15 21:45:26 -070092 public String getUserData(final Account account, final String key) {
Fred Quintana60307342009-03-24 22:48:12 -070093 try {
94 return mService.getUserData(account, key);
95 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -070096 // will never happen
Fred Quintana60307342009-03-24 22:48:12 -070097 throw new RuntimeException(e);
98 }
99 }
100
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700101 public AuthenticatorDescription[] getAuthenticatorTypes() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700102 try {
103 return mService.getAuthenticatorTypes();
104 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700105 // will never happen
Fred Quintanaa698f422009-04-08 19:14:54 -0700106 throw new RuntimeException(e);
107 }
108 }
109
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700110 public Account[] getAccounts() {
Fred Quintana60307342009-03-24 22:48:12 -0700111 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700112 return mService.getAccounts(null);
Fred Quintana60307342009-03-24 22:48:12 -0700113 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700114 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700115 throw new RuntimeException(e);
116 }
117 }
118
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700119 public Account[] getAccountsByType(String type) {
Fred Quintana60307342009-03-24 22:48:12 -0700120 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700121 return mService.getAccounts(type);
Fred Quintana60307342009-03-24 22:48:12 -0700122 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700123 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700124 throw new RuntimeException(e);
125 }
126 }
127
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700128 public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
Fred Quintana60307342009-03-24 22:48:12 -0700129 try {
130 return mService.addAccount(account, password, extras);
131 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700132 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700133 throw new RuntimeException(e);
134 }
135 }
136
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700137 public AccountManagerFuture<Boolean> removeAccount(final Account account,
138 AccountManagerCallback<Boolean> callback, Handler handler) {
139 return new Future2Task<Boolean>(handler, callback) {
140 public void doWork() throws RemoteException {
141 mService.removeAccount(mResponse, account);
Fred Quintanaa698f422009-04-08 19:14:54 -0700142 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700143 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
144 if (!bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) {
145 throw new AuthenticatorException("no result in response");
146 }
147 return bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY);
Fred Quintanaa698f422009-04-08 19:14:54 -0700148 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700149 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700150 }
151
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700152 public void invalidateAuthToken(final String accountType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700153 try {
154 mService.invalidateAuthToken(accountType, authToken);
155 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700156 // won't ever happen
Fred Quintana60307342009-03-24 22:48:12 -0700157 throw new RuntimeException(e);
158 }
159 }
160
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700161 public String peekAuthToken(final Account account, final String authTokenType) {
162 try {
163 return mService.peekAuthToken(account, authTokenType);
164 } catch (RemoteException e) {
165 // won't ever happen
166 throw new RuntimeException(e);
167 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700168 }
169
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700170 public void setPassword(final Account account, final String password) {
Fred Quintana60307342009-03-24 22:48:12 -0700171 try {
172 mService.setPassword(account, password);
173 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700174 // won't ever happen
175 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700176 }
177 }
178
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700179 public void clearPassword(final Account account) {
Fred Quintana60307342009-03-24 22:48:12 -0700180 try {
181 mService.clearPassword(account);
182 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700183 // won't ever happen
184 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700185 }
186 }
187
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700188 public void setUserData(final Account account, final String key, final String value) {
Fred Quintana60307342009-03-24 22:48:12 -0700189 try {
190 mService.setUserData(account, key, value);
191 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700192 // won't ever happen
193 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700194 }
195 }
196
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700197 public void setAuthToken(Account account, final String authTokenType, final String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700198 try {
199 mService.setAuthToken(account, authTokenType, authToken);
200 } catch (RemoteException e) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700201 // won't ever happen
202 throw new RuntimeException(e);
Fred Quintana60307342009-03-24 22:48:12 -0700203 }
204 }
205
Fred Quintanaa698f422009-04-08 19:14:54 -0700206 public String blockingGetAuthToken(Account account, String authTokenType,
207 boolean notifyAuthFailure)
208 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaa698f422009-04-08 19:14:54 -0700209 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
210 null /* handler */).getResult();
211 return bundle.getString(Constants.AUTHTOKEN_KEY);
212 }
213
214 /**
215 * Request the auth token for this account/authTokenType. If this succeeds then the
216 * auth token will then be passed to the activity. If this results in an authentication
217 * failure then a login intent will be returned that can be invoked to prompt the user to
218 * update their credentials. This login activity will return the auth token to the calling
219 * activity. If activity is null then the login intent will not be invoked.
220 *
221 * @param account the account whose auth token should be retrieved
222 * @param authTokenType the auth token type that should be retrieved
223 * @param loginOptions
224 * @param activity the activity to launch the login intent, if necessary, and to which
225 */
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700226 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintanaa698f422009-04-08 19:14:54 -0700227 final Account account, final String authTokenType, final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700228 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700229 if (activity == null) throw new IllegalArgumentException("activity is null");
230 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
231 return new AmsTask(activity, handler, callback) {
232 public void doWork() throws RemoteException {
233 mService.getAuthToken(mResponse, account, authTokenType,
234 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
235 loginOptions);
236 }
Fred Quintana33269202009-04-20 16:05:10 -0700237 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700238 }
239
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700240 public AccountManagerFuture<Bundle> getAuthToken(
Fred Quintanaa698f422009-04-08 19:14:54 -0700241 final Account account, final String authTokenType, final boolean notifyAuthFailure,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700242 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700243 if (account == null) throw new IllegalArgumentException("account is null");
244 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
245 return new AmsTask(null, handler, callback) {
246 public void doWork() throws RemoteException {
247 mService.getAuthToken(mResponse, account, authTokenType,
248 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
249 }
Fred Quintana33269202009-04-20 16:05:10 -0700250 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700251 }
252
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700253 public AccountManagerFuture<Bundle> addAccount(final String accountType,
Fred Quintana33269202009-04-20 16:05:10 -0700254 final String authTokenType, final String[] requiredFeatures,
255 final Bundle addAccountOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700256 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700257 return new AmsTask(activity, handler, callback) {
258 public void doWork() throws RemoteException {
259 mService.addAcount(mResponse, accountType, authTokenType,
Fred Quintana33269202009-04-20 16:05:10 -0700260 requiredFeatures, activity != null, addAccountOptions);
Fred Quintanaa698f422009-04-08 19:14:54 -0700261 }
Fred Quintana33269202009-04-20 16:05:10 -0700262 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700263 }
264
265 /** @deprecated use {@link #confirmCredentials} instead */
Dianne Hackborn4a51c202009-08-21 15:14:02 -0700266 @Deprecated
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700267 public AccountManagerFuture<Boolean> confirmPassword(final Account account, final String password,
268 AccountManagerCallback<Boolean> callback, Handler handler) {
269 return new Future2Task<Boolean>(handler, callback) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700270 public void doWork() throws RemoteException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700271 mService.confirmPassword(mResponse, account, password);
Fred Quintanaa698f422009-04-08 19:14:54 -0700272 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700273 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
274 if (!bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) {
275 throw new AuthenticatorException("no result in response");
276 }
277 return bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY);
Fred Quintana33269202009-04-20 16:05:10 -0700278 }
279 }.start();
280 }
281
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700282 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
283 final String type, final String[] features,
284 AccountManagerCallback<Account[]> callback, Handler handler) {
285 if (type == null) throw new IllegalArgumentException("type is null");
286 return new Future2Task<Account[]>(handler, callback) {
287 public void doWork() throws RemoteException {
288 mService.getAccountsByFeatures(mResponse, type, features);
289 }
290 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
291 if (!bundle.containsKey(Constants.ACCOUNTS_KEY)) {
292 throw new AuthenticatorException("no result in response");
293 }
294 final Parcelable[] parcelables = bundle.getParcelableArray(Constants.ACCOUNTS_KEY);
295 Account[] descs = new Account[parcelables.length];
296 for (int i = 0; i < parcelables.length; i++) {
297 descs[i] = (Account) parcelables[i];
298 }
299 return descs;
300 }
301 }.start();
302 }
303
304 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Activity activity,
305 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700306 final Handler handler) {
307 return new AmsTask(activity, handler, callback) {
308 public void doWork() throws RemoteException {
309 mService.confirmCredentials(mResponse, account, activity != null);
310 }
Fred Quintana33269202009-04-20 16:05:10 -0700311 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700312 }
313
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700314 public AccountManagerFuture<Bundle> updateCredentials(final Account account, final String authTokenType,
Fred Quintanaa698f422009-04-08 19:14:54 -0700315 final Bundle loginOptions, final Activity activity,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700316 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700317 final Handler handler) {
318 return new AmsTask(activity, handler, callback) {
319 public void doWork() throws RemoteException {
320 mService.updateCredentials(mResponse, account, authTokenType, activity != null,
321 loginOptions);
322 }
Fred Quintana33269202009-04-20 16:05:10 -0700323 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700324 }
325
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700326 public AccountManagerFuture<Bundle> editProperties(final String accountType, final Activity activity,
327 final AccountManagerCallback<Bundle> callback,
Fred Quintanaa698f422009-04-08 19:14:54 -0700328 final Handler handler) {
329 return new AmsTask(activity, handler, callback) {
330 public void doWork() throws RemoteException {
331 mService.editProperties(mResponse, accountType, activity != null);
332 }
Fred Quintana33269202009-04-20 16:05:10 -0700333 }.start();
Fred Quintanaa698f422009-04-08 19:14:54 -0700334 }
335
336 private void ensureNotOnMainThread() {
337 final Looper looper = Looper.myLooper();
338 if (looper != null && looper == mContext.getMainLooper()) {
339 // We really want to throw an exception here, but GTalkService exercises this
340 // path quite a bit and needs some serious rewrite in order to work properly.
341 //noinspection ThrowableInstanceNeverThrow
342// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
343// new Exception());
344 // TODO(fredq) remove the log and throw this exception when the callers are fixed
345// throw new IllegalStateException(
346// "calling this from your main thread can lead to deadlock");
Fred Quintana60307342009-03-24 22:48:12 -0700347 }
348 }
349
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700350 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
351 final AccountManagerFuture<Bundle> future) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700352 handler = handler == null ? mMainHandler : handler;
353 handler.post(new Runnable() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700354 public void run() {
355 callback.run(future);
356 }
357 });
358 }
Fred Quintana60307342009-03-24 22:48:12 -0700359
Fred Quintanad9d2f112009-04-23 13:36:27 -0700360 private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener,
361 final Account[] accounts) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700362 final Account[] accountsCopy = new Account[accounts.length];
363 // send a copy to make sure that one doesn't
364 // change what another sees
365 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
366 handler = (handler == null) ? mMainHandler : handler;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700367 handler.post(new Runnable() {
368 public void run() {
Costin Manolacheb6437242009-09-10 16:14:12 -0700369 try {
370 listener.onAccountsUpdated(accountsCopy);
371 } catch (SQLException e) {
372 // Better luck next time. If the problem was disk-full,
373 // the STORAGE_OK intent will re-trigger the update.
374 Log.e(TAG, "Can't update accounts", e);
375 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700376 }
377 });
378 }
379
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700380 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
Fred Quintanaa698f422009-04-08 19:14:54 -0700381 final IAccountManagerResponse mResponse;
382 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700383 final AccountManagerCallback<Bundle> mCallback;
Fred Quintanaa698f422009-04-08 19:14:54 -0700384 final Activity mActivity;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700385 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700386 super(new Callable<Bundle>() {
387 public Bundle call() throws Exception {
388 throw new IllegalStateException("this should never be called");
389 }
390 });
391
392 mHandler = handler;
393 mCallback = callback;
394 mActivity = activity;
395 mResponse = new Response();
Fred Quintana33269202009-04-20 16:05:10 -0700396 }
397
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700398 public final AccountManagerFuture<Bundle> start() {
399 try {
400 doWork();
401 } catch (RemoteException e) {
402 setException(e);
403 }
Fred Quintana33269202009-04-20 16:05:10 -0700404 return this;
Fred Quintana60307342009-03-24 22:48:12 -0700405 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700406
407 public abstract void doWork() throws RemoteException;
408
409 private Bundle internalGetResult(Long timeout, TimeUnit unit)
410 throws OperationCanceledException, IOException, AuthenticatorException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700411 ensureNotOnMainThread();
Fred Quintanaa698f422009-04-08 19:14:54 -0700412 try {
413 if (timeout == null) {
414 return get();
415 } else {
416 return get(timeout, unit);
417 }
418 } catch (CancellationException e) {
419 throw new OperationCanceledException();
420 } catch (TimeoutException e) {
421 // fall through and cancel
422 } catch (InterruptedException e) {
423 // fall through and cancel
424 } catch (ExecutionException e) {
425 final Throwable cause = e.getCause();
426 if (cause instanceof IOException) {
427 throw (IOException) cause;
428 } else if (cause instanceof UnsupportedOperationException) {
429 throw new AuthenticatorException(cause);
430 } else if (cause instanceof AuthenticatorException) {
431 throw (AuthenticatorException) cause;
432 } else if (cause instanceof RuntimeException) {
433 throw (RuntimeException) cause;
434 } else if (cause instanceof Error) {
435 throw (Error) cause;
436 } else {
437 throw new IllegalStateException(cause);
438 }
439 } finally {
440 cancel(true /* interrupt if running */);
441 }
442 throw new OperationCanceledException();
443 }
444
445 public Bundle getResult()
446 throws OperationCanceledException, IOException, AuthenticatorException {
447 return internalGetResult(null, null);
448 }
449
450 public Bundle getResult(long timeout, TimeUnit unit)
451 throws OperationCanceledException, IOException, AuthenticatorException {
452 return internalGetResult(timeout, unit);
453 }
454
455 protected void done() {
456 if (mCallback != null) {
457 postToHandler(mHandler, mCallback, this);
458 }
459 }
460
461 /** Handles the responses from the AccountManager */
462 private class Response extends IAccountManagerResponse.Stub {
463 public void onResult(Bundle bundle) {
464 Intent intent = bundle.getParcelable("intent");
465 if (intent != null && mActivity != null) {
466 // since the user provided an Activity we will silently start intents
467 // that we see
468 mActivity.startActivity(intent);
469 // leave the Future running to wait for the real response to this request
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700470 } else if (bundle.getBoolean("retry")) {
471 try {
472 doWork();
473 } catch (RemoteException e) {
474 // this will only happen if the system process is dead, which means
475 // we will be dying ourselves
476 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700477 } else {
478 set(bundle);
479 }
480 }
481
482 public void onError(int code, String message) {
483 if (code == Constants.ERROR_CODE_CANCELED) {
484 // the authenticator indicated that this request was canceled, do so now
485 cancel(true /* mayInterruptIfRunning */);
486 return;
487 }
488 setException(convertErrorToException(code, message));
489 }
490 }
491
Fred Quintana60307342009-03-24 22:48:12 -0700492 }
493
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700494 private abstract class BaseFutureTask<T> extends FutureTask<T> {
495 final public IAccountManagerResponse mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700496 final Handler mHandler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700497
498 public BaseFutureTask(Handler handler) {
499 super(new Callable<T>() {
500 public T call() throws Exception {
Fred Quintanaa698f422009-04-08 19:14:54 -0700501 throw new IllegalStateException("this should never be called");
502 }
503 });
Fred Quintanaa698f422009-04-08 19:14:54 -0700504 mHandler = handler;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700505 mResponse = new Response();
Fred Quintana60307342009-03-24 22:48:12 -0700506 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700507
508 public abstract void doWork() throws RemoteException;
509
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700510 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
Fred Quintanaa698f422009-04-08 19:14:54 -0700511
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700512 protected void postRunnableToHandler(Runnable runnable) {
513 Handler handler = (mHandler == null) ? mMainHandler : mHandler;
514 handler.post(runnable);
Fred Quintanaa698f422009-04-08 19:14:54 -0700515 }
516
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700517 protected void startTask() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700518 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700519 doWork();
520 } catch (RemoteException e) {
521 setException(e);
Fred Quintanaa698f422009-04-08 19:14:54 -0700522 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700523 }
524
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700525 protected class Response extends IAccountManagerResponse.Stub {
Fred Quintanaa698f422009-04-08 19:14:54 -0700526 public void onResult(Bundle bundle) {
527 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700528 T result = bundleToResult(bundle);
529 if (result == null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700530 return;
531 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700532 set(result);
533 return;
Fred Quintanaa698f422009-04-08 19:14:54 -0700534 } catch (ClassCastException e) {
535 // we will set the exception below
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700536 } catch (AuthenticatorException e) {
537 // we will set the exception below
Fred Quintanaa698f422009-04-08 19:14:54 -0700538 }
539 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response");
540 }
541
542 public void onError(int code, String message) {
543 if (code == Constants.ERROR_CODE_CANCELED) {
544 cancel(true /* mayInterruptIfRunning */);
545 return;
546 }
547 setException(convertErrorToException(code, message));
548 }
549 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700550 }
551
552 private abstract class Future2Task<T>
553 extends BaseFutureTask<T> implements AccountManagerFuture<T> {
554 final AccountManagerCallback<T> mCallback;
555 public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
556 super(handler);
557 mCallback = callback;
558 }
559
560 protected void done() {
561 if (mCallback != null) {
562 postRunnableToHandler(new Runnable() {
563 public void run() {
564 mCallback.run(Future2Task.this);
565 }
566 });
567 }
568 }
569
570 public Future2Task<T> start() {
571 startTask();
572 return this;
573 }
574
575 private T internalGetResult(Long timeout, TimeUnit unit)
576 throws OperationCanceledException, IOException, AuthenticatorException {
577 ensureNotOnMainThread();
578 try {
579 if (timeout == null) {
580 return get();
581 } else {
582 return get(timeout, unit);
583 }
584 } catch (InterruptedException e) {
585 // fall through and cancel
586 } catch (TimeoutException e) {
587 // fall through and cancel
588 } catch (CancellationException e) {
589 // fall through and cancel
590 } catch (ExecutionException e) {
591 final Throwable cause = e.getCause();
592 if (cause instanceof IOException) {
593 throw (IOException) cause;
594 } else if (cause instanceof UnsupportedOperationException) {
595 throw new AuthenticatorException(cause);
596 } else if (cause instanceof AuthenticatorException) {
597 throw (AuthenticatorException) cause;
598 } else if (cause instanceof RuntimeException) {
599 throw (RuntimeException) cause;
600 } else if (cause instanceof Error) {
601 throw (Error) cause;
602 } else {
603 throw new IllegalStateException(cause);
604 }
605 } finally {
606 cancel(true /* interrupt if running */);
607 }
608 throw new OperationCanceledException();
609 }
610
611 public T getResult()
612 throws OperationCanceledException, IOException, AuthenticatorException {
613 return internalGetResult(null, null);
614 }
615
616 public T getResult(long timeout, TimeUnit unit)
617 throws OperationCanceledException, IOException, AuthenticatorException {
618 return internalGetResult(timeout, unit);
619 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700620
Fred Quintana60307342009-03-24 22:48:12 -0700621 }
622
Fred Quintanaa698f422009-04-08 19:14:54 -0700623 private Exception convertErrorToException(int code, String message) {
624 if (code == Constants.ERROR_CODE_NETWORK_ERROR) {
625 return new IOException(message);
Fred Quintana60307342009-03-24 22:48:12 -0700626 }
Fred Quintana60307342009-03-24 22:48:12 -0700627
Fred Quintanaa698f422009-04-08 19:14:54 -0700628 if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) {
Fred Quintana33269202009-04-20 16:05:10 -0700629 return new UnsupportedOperationException(message);
Fred Quintana60307342009-03-24 22:48:12 -0700630 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700631
632 if (code == Constants.ERROR_CODE_INVALID_RESPONSE) {
Fred Quintana33269202009-04-20 16:05:10 -0700633 return new AuthenticatorException(message);
Fred Quintanaa698f422009-04-08 19:14:54 -0700634 }
635
Fred Quintana33269202009-04-20 16:05:10 -0700636 if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) {
637 return new IllegalArgumentException(message);
638 }
639
640 return new AuthenticatorException(message);
641 }
642
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700643 private class GetAuthTokenByTypeAndFeaturesTask
644 extends AmsTask implements AccountManagerCallback<Bundle> {
Fred Quintana33269202009-04-20 16:05:10 -0700645 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
646 final String[] features, Activity activityForPrompting,
647 final Bundle addAccountOptions, final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700648 AccountManagerCallback<Bundle> callback, Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -0700649 super(activityForPrompting, handler, callback);
650 if (accountType == null) throw new IllegalArgumentException("account type is null");
651 mAccountType = accountType;
652 mAuthTokenType = authTokenType;
653 mFeatures = features;
654 mAddAccountOptions = addAccountOptions;
655 mLoginOptions = loginOptions;
656 mMyCallback = this;
657 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700658 volatile AccountManagerFuture<Bundle> mFuture = null;
Fred Quintana33269202009-04-20 16:05:10 -0700659 final String mAccountType;
660 final String mAuthTokenType;
661 final String[] mFeatures;
662 final Bundle mAddAccountOptions;
663 final Bundle mLoginOptions;
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700664 final AccountManagerCallback<Bundle> mMyCallback;
Fred Quintana33269202009-04-20 16:05:10 -0700665
666 public void doWork() throws RemoteException {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700667 getAccountsByTypeAndFeatures(mAccountType, mFeatures,
668 new AccountManagerCallback<Account[]>() {
669 public void run(AccountManagerFuture<Account[]> future) {
670 Account[] accounts;
Fred Quintana33269202009-04-20 16:05:10 -0700671 try {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700672 accounts = future.getResult();
673 } catch (OperationCanceledException e) {
674 setException(e);
675 return;
676 } catch (IOException e) {
677 setException(e);
678 return;
679 } catch (AuthenticatorException e) {
680 setException(e);
681 return;
Fred Quintana33269202009-04-20 16:05:10 -0700682 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700683
684 if (accounts.length == 0) {
685 if (mActivity != null) {
686 // no accounts, add one now. pretend that the user directly
687 // made this request
688 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
689 mAddAccountOptions, mActivity, mMyCallback, mHandler);
690 } else {
691 // send result since we can't prompt to add an account
692 Bundle result = new Bundle();
693 result.putString(Constants.ACCOUNT_NAME_KEY, null);
694 result.putString(Constants.ACCOUNT_TYPE_KEY, null);
695 result.putString(Constants.AUTHTOKEN_KEY, null);
696 try {
697 mResponse.onResult(result);
698 } catch (RemoteException e) {
699 // this will never happen
700 }
701 // we are done
702 }
703 } else if (accounts.length == 1) {
704 // have a single account, return an authtoken for it
705 if (mActivity == null) {
706 mFuture = getAuthToken(accounts[0], mAuthTokenType,
707 false /* notifyAuthFailure */, mMyCallback, mHandler);
708 } else {
709 mFuture = getAuthToken(accounts[0],
710 mAuthTokenType, mLoginOptions,
Fred Quintana33269202009-04-20 16:05:10 -0700711 mActivity, mMyCallback, mHandler);
712 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700713 } else {
714 if (mActivity != null) {
715 IAccountManagerResponse chooseResponse =
716 new IAccountManagerResponse.Stub() {
717 public void onResult(Bundle value) throws RemoteException {
718 Account account = new Account(
719 value.getString(Constants.ACCOUNT_NAME_KEY),
720 value.getString(Constants.ACCOUNT_TYPE_KEY));
721 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
722 mActivity, mMyCallback, mHandler);
723 }
Fred Quintana33269202009-04-20 16:05:10 -0700724
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700725 public void onError(int errorCode, String errorMessage)
726 throws RemoteException {
727 mResponse.onError(errorCode, errorMessage);
728 }
729 };
730 // have many accounts, launch the chooser
731 Intent intent = new Intent();
732 intent.setClassName("android",
733 "android.accounts.ChooseAccountActivity");
734 intent.putExtra(Constants.ACCOUNTS_KEY, accounts);
735 intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY,
736 new AccountManagerResponse(chooseResponse));
737 mActivity.startActivity(intent);
738 // the result will arrive via the IAccountManagerResponse
739 } else {
740 // send result since we can't prompt to select an account
741 Bundle result = new Bundle();
742 result.putString(Constants.ACCOUNTS_KEY, null);
743 try {
744 mResponse.onResult(result);
745 } catch (RemoteException e) {
746 // this will never happen
747 }
748 // we are done
Fred Quintana33269202009-04-20 16:05:10 -0700749 }
Fred Quintana33269202009-04-20 16:05:10 -0700750 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700751 }}, mHandler);
Fred Quintana33269202009-04-20 16:05:10 -0700752 }
753
754
755
756 // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying
757 // future that we create. We need to do things like have cancel cancel the mFuture, if set
758 // or to cause this to be canceled if mFuture isn't set.
759 // Once this is done then getAuthTokenByFeatures can be changed to return a Future2.
760
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700761 public void run(AccountManagerFuture<Bundle> future) {
Fred Quintana33269202009-04-20 16:05:10 -0700762 try {
763 set(future.get());
764 } catch (InterruptedException e) {
765 cancel(true);
766 } catch (CancellationException e) {
767 cancel(true);
768 } catch (ExecutionException e) {
769 setException(e.getCause());
770 }
771 }
772 }
773
774 public void getAuthTokenByFeatures(
775 final String accountType, final String authTokenType, final String[] features,
776 final Activity activityForPrompting, final Bundle addAccountOptions,
777 final Bundle loginOptions,
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700778 final AccountManagerCallback<Bundle> callback, final Handler handler) {
Fred Quintana33269202009-04-20 16:05:10 -0700779 if (accountType == null) throw new IllegalArgumentException("account type is null");
780 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
781 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
782 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start();
Fred Quintana60307342009-03-24 22:48:12 -0700783 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700784
785 private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners =
786 Maps.newHashMap();
787
Fred Quintanad9d2f112009-04-23 13:36:27 -0700788 /**
789 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
790 * so that it can read the updated list of accounts and send them to the listener
791 * in mAccountsUpdatedListeners.
792 */
793 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
794 public void onReceive(final Context context, final Intent intent) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700795 final Account[] accounts = getAccounts();
796 // send the result to the listeners
797 synchronized (mAccountsUpdatedListeners) {
798 for (Map.Entry<OnAccountsUpdatedListener, Handler> entry :
799 mAccountsUpdatedListeners.entrySet()) {
800 postToHandler(entry.getValue(), entry.getKey(), accounts);
Fred Quintanad9d2f112009-04-23 13:36:27 -0700801 }
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700802 }
Fred Quintanad9d2f112009-04-23 13:36:27 -0700803 }
804 };
805
806 /**
807 * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}.
808 * The listener is guaranteed to be invoked on the thread of the Handler that is passed
809 * in or the main thread's Handler if handler is null.
Fred Quintanae00a3112009-09-22 15:13:30 -0700810 * <p>
811 * You must remove this listener before the context that was used to retrieve this
812 * {@link AccountManager} instance goes away. This generally means when the Activity
813 * or Service you are running is stopped.
Fred Quintanad9d2f112009-04-23 13:36:27 -0700814 * @param listener the listener to add
815 * @param handler the Handler whose thread will be used to invoke the listener. If null
816 * the AccountManager context's main thread will be used.
817 * @param updateImmediately if true then the listener will be invoked as a result of this
818 * call.
819 * @throws IllegalArgumentException if listener is null
820 * @throws IllegalStateException if listener was already added
821 */
822 public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener,
823 Handler handler, boolean updateImmediately) {
824 if (listener == null) {
825 throw new IllegalArgumentException("the listener is null");
826 }
827 synchronized (mAccountsUpdatedListeners) {
828 if (mAccountsUpdatedListeners.containsKey(listener)) {
829 throw new IllegalStateException("this listener is already added");
830 }
831 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
832
833 mAccountsUpdatedListeners.put(listener, handler);
834
835 if (wasEmpty) {
836 // Register a broadcast receiver to monitor account changes
837 IntentFilter intentFilter = new IntentFilter();
838 intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
Costin Manolacheb6437242009-09-10 16:14:12 -0700839 // To recover from disk-full.
840 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
Fred Quintanad9d2f112009-04-23 13:36:27 -0700841 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
842 }
843 }
844
845 if (updateImmediately) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700846 postToHandler(handler, listener, getAccounts());
Fred Quintanad9d2f112009-04-23 13:36:27 -0700847 }
848 }
849
850 /**
851 * Remove an {@link OnAccountsUpdatedListener} that was previously registered with
852 * {@link #addOnAccountsUpdatedListener}.
853 * @param listener the listener to remove
854 * @throws IllegalArgumentException if listener is null
855 * @throws IllegalStateException if listener was not already added
856 */
857 public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) {
858 if (listener == null) {
859 throw new IllegalArgumentException("the listener is null");
860 }
861 synchronized (mAccountsUpdatedListeners) {
862 if (mAccountsUpdatedListeners.remove(listener) == null) {
863 throw new IllegalStateException("this listener was not previously added");
864 }
865 if (mAccountsUpdatedListeners.isEmpty()) {
866 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
867 }
868 }
869 }
Fred Quintana60307342009-03-24 22:48:12 -0700870}