diff --git a/api/current.xml b/api/current.xml
index adb1954a2f5a5..9125da5406cc6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -12569,6 +12569,8 @@
+
+
@@ -12655,6 +12657,25 @@
visibility="public"
>
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
@@ -13135,6 +13146,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14840,6 +14431,25 @@
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
implements Future2 {
+ private abstract class AmsTask extends FutureTask implements Future2 {
final IAccountManagerResponse mResponse;
final Handler mHandler;
final Future2Callback mCallback;
final Activity mActivity;
+ final Thread mThread;
public AmsTask(Activity activity, Handler handler, Future2Callback callback) {
super(new Callable() {
public Bundle call() throws Exception {
@@ -518,8 +551,7 @@ public class AccountManager {
mCallback = callback;
mActivity = activity;
mResponse = new Response();
-
- new Thread(new Runnable() {
+ mThread = new Thread(new Runnable() {
public void run() {
try {
doWork();
@@ -527,7 +559,12 @@ public class AccountManager {
// never happens
}
}
- }).start();
+ }, "AmsTask");
+ }
+
+ public final Future2 start() {
+ mThread.start();
+ return this;
}
public abstract void doWork() throws RemoteException;
@@ -609,7 +646,7 @@ public class AccountManager {
}
- public abstract class AMSTaskBoolean extends FutureTask implements Future1 {
+ private abstract class AMSTaskBoolean extends FutureTask implements Future1 {
final IAccountManagerResponse response;
final Handler mHandler;
final Future1Callback mCallback;
@@ -716,13 +753,159 @@ public class AccountManager {
}
if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) {
- return new UnsupportedOperationException();
+ return new UnsupportedOperationException(message);
}
if (code == Constants.ERROR_CODE_INVALID_RESPONSE) {
- return new AuthenticatorException("invalid response");
+ return new AuthenticatorException(message);
}
- return new AuthenticatorException("unknown error code");
+ if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) {
+ return new IllegalArgumentException(message);
+ }
+
+ return new AuthenticatorException(message);
+ }
+
+ private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback {
+ GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
+ final String[] features, Activity activityForPrompting,
+ final Bundle addAccountOptions, final Bundle loginOptions,
+ Future2Callback callback, Handler handler) {
+ super(activityForPrompting, handler, callback);
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ mAccountType = accountType;
+ mAuthTokenType = authTokenType;
+ mFeatures = features;
+ mAddAccountOptions = addAccountOptions;
+ mLoginOptions = loginOptions;
+ mMyCallback = this;
+ }
+ volatile Future2 mFuture = null;
+ final String mAccountType;
+ final String mAuthTokenType;
+ final String[] mFeatures;
+ final Bundle mAddAccountOptions;
+ final Bundle mLoginOptions;
+ final Future2Callback mMyCallback;
+
+ public void doWork() throws RemoteException {
+ getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() {
+ public void run(Future2 future) {
+ Bundle getAccountsResult;
+ try {
+ getAccountsResult = future.getResult();
+ } catch (OperationCanceledException e) {
+ setException(e);
+ return;
+ } catch (IOException e) {
+ setException(e);
+ return;
+ } catch (AuthenticatorException e) {
+ setException(e);
+ return;
+ }
+
+ Parcelable[] accounts =
+ getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY);
+ if (accounts.length == 0) {
+ if (mActivity != null) {
+ // no accounts, add one now. pretend that the user directly
+ // made this request
+ mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
+ mAddAccountOptions, mActivity, mMyCallback, mHandler);
+ } else {
+ // send result since we can't prompt to add an account
+ Bundle result = new Bundle();
+ result.putString(Constants.ACCOUNT_NAME_KEY, null);
+ result.putString(Constants.ACCOUNT_TYPE_KEY, null);
+ result.putString(Constants.AUTHTOKEN_KEY, null);
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this will never happen
+ }
+ // we are done
+ }
+ } else if (accounts.length == 1) {
+ // have a single account, return an authtoken for it
+ if (mActivity == null) {
+ mFuture = getAuthToken((Account) accounts[0], mAuthTokenType,
+ false /* notifyAuthFailure */, mMyCallback, mHandler);
+ } else {
+ mFuture = getAuthToken((Account) accounts[0],
+ mAuthTokenType, mLoginOptions,
+ mActivity, mMyCallback, mHandler);
+ }
+ } else {
+ if (mActivity != null) {
+ IAccountManagerResponse chooseResponse =
+ new IAccountManagerResponse.Stub() {
+ public void onResult(Bundle value) throws RemoteException {
+ Account account = new Account(
+ value.getString(Constants.ACCOUNT_NAME_KEY),
+ value.getString(Constants.ACCOUNT_TYPE_KEY));
+ mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
+ mActivity, mMyCallback, mHandler);
+ }
+
+ public void onError(int errorCode, String errorMessage)
+ throws RemoteException {
+ mResponse.onError(errorCode, errorMessage);
+ }
+ };
+ // have many accounts, launch the chooser
+ Intent intent = new Intent();
+ intent.setClassName("android",
+ "android.accounts.ChooseAccountActivity");
+ intent.putExtra(Constants.ACCOUNTS_KEY, accounts);
+ intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY,
+ new AccountManagerResponse(chooseResponse));
+ mActivity.startActivity(intent);
+ // the result will arrive via the IAccountManagerResponse
+ } else {
+ // send result since we can't prompt to select an account
+ Bundle result = new Bundle();
+ result.putString(Constants.ACCOUNTS_KEY, null);
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this will never happen
+ }
+ // we are done
+ }
+ }
+ }}, mHandler);
+ }
+
+
+
+ // TODO(fredq) pass through the calls to our implemention of Future2 to the underlying
+ // future that we create. We need to do things like have cancel cancel the mFuture, if set
+ // or to cause this to be canceled if mFuture isn't set.
+ // Once this is done then getAuthTokenByFeatures can be changed to return a Future2.
+
+ public void run(Future2 future) {
+ try {
+ set(future.get());
+ } catch (InterruptedException e) {
+ cancel(true);
+ } catch (CancellationException e) {
+ cancel(true);
+ } catch (ExecutionException e) {
+ setException(e.getCause());
+ }
+ }
+ }
+
+ public void getAuthTokenByFeatures(
+ final String accountType, final String authTokenType, final String[] features,
+ final Activity activityForPrompting, final Bundle addAccountOptions,
+ final Bundle loginOptions,
+ final Future2Callback callback, final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
+ activityForPrompting, addAccountOptions, loginOptions, callback, handler).start();
}
}
diff --git a/core/java/android/accounts/AccountManagerResponse.java b/core/java/android/accounts/AccountManagerResponse.java
new file mode 100644
index 0000000000000..25371fd9cbb5d
--- /dev/null
+++ b/core/java/android/accounts/AccountManagerResponse.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accounts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Object that wraps calls to an {@link android.accounts.IAccountManagerResponse} object.
+ * @hide
+ */
+public class AccountManagerResponse implements Parcelable {
+ private IAccountManagerResponse mResponse;
+
+ public AccountManagerResponse(IAccountManagerResponse response) {
+ mResponse = response;
+ }
+
+ public AccountManagerResponse(Parcel parcel) {
+ mResponse =
+ IAccountManagerResponse.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ public void onResult(Bundle result) {
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ try {
+ mResponse.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mResponse.asBinder());
+ }
+
+ public static final Creator CREATOR =
+ new Creator() {
+ public AccountManagerResponse createFromParcel(Parcel source) {
+ return new AccountManagerResponse(source);
+ }
+
+ public AccountManagerResponse[] newArray(int size) {
+ return new AccountManagerResponse[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 1f4362021fbfe..ef0875c670552 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -59,6 +59,7 @@ import com.google.android.collect.Maps;
* instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
* AccountManager accountManager =
* (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
+ * @hide
*/
public class AccountManagerService extends IAccountManager.Stub {
private static final String TAG = "AccountManagerService";
@@ -79,7 +80,6 @@ public class AccountManagerService extends IAccountManager.Stub {
private final AccountAuthenticatorCache mAuthenticatorCache;
private final AuthenticatorBindHelper mBindHelper;
- public final HashMap mAuthTokenCache = Maps.newHashMap();
private final DatabaseHelper mOpenHelper;
private final SimWatcher mSimWatcher;
@@ -300,7 +300,7 @@ public class AccountManagerService extends IAccountManager.Stub {
}
}
db.setTransactionSuccessful();
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+ sendAccountsChangedBroadcast();
return true;
} finally {
db.endTransaction();
@@ -321,22 +321,10 @@ public class AccountManagerService extends IAccountManager.Stub {
public void removeAccount(Account account) {
long identityToken = clearCallingIdentity();
try {
- synchronized (mAuthTokenCache) {
- ArrayList keysToRemove = Lists.newArrayList();
- for (AuthTokenKey key : mAuthTokenCache.keySet()) {
- if (key.mAccount.equals(account)) {
- keysToRemove.add(key);
- }
- }
- for (AuthTokenKey key : keysToRemove) {
- mAuthTokenCache.remove(key);
- }
-
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.mName, account.mType});
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
- }
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[]{account.mName, account.mType});
+ sendAccountsChangedBroadcast();
} finally {
restoreCallingIdentity(identityToken);
}
@@ -359,31 +347,26 @@ public class AccountManagerService extends IAccountManager.Stub {
}
private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
- synchronized (mAuthTokenCache) {
- Cursor cursor = db.rawQuery(
- "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
- + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
- + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
- + " FROM " + TABLE_ACCOUNTS
- + " JOIN " + TABLE_AUTHTOKENS
- + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
- + " = " + AUTHTOKENS_ACCOUNTS_ID
- + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
- + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
- new String[]{authToken, accountType});
- try {
- while (cursor.moveToNext()) {
- long authTokenId = cursor.getLong(0);
- String accountName = cursor.getString(1);
- String authTokenType = cursor.getString(2);
- AuthTokenKey key = new AuthTokenKey(new Account(accountName, accountType),
- authTokenType);
- mAuthTokenCache.remove(key);
- db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
- }
- } finally {
- cursor.close();
+ Cursor cursor = db.rawQuery(
+ "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+ + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+ + " FROM " + TABLE_ACCOUNTS
+ + " JOIN " + TABLE_AUTHTOKENS
+ + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ + " = " + AUTHTOKENS_ACCOUNTS_ID
+ + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
+ + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
+ new String[]{authToken, accountType});
+ try {
+ while (cursor.moveToNext()) {
+ long authTokenId = cursor.getLong(0);
+ String accountName = cursor.getString(1);
+ String authTokenType = cursor.getString(2);
+ db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
}
+ } finally {
+ cursor.close();
}
}
@@ -391,8 +374,18 @@ public class AccountManagerService extends IAccountManager.Stub {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
- if (saveAuthTokenToDatabase(db, account, type, authToken)) {
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+ long accountId = getAccountId(db, account);
+ if (accountId < 0) {
+ return false;
+ }
+ db.delete(TABLE_AUTHTOKENS,
+ AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+ new String[]{type});
+ ContentValues values = new ContentValues();
+ values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
+ values.put(AUTHTOKENS_TYPE, type);
+ values.put(AUTHTOKENS_AUTHTOKEN, authToken);
+ if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
db.setTransactionSuccessful();
return true;
}
@@ -402,22 +395,6 @@ public class AccountManagerService extends IAccountManager.Stub {
}
}
- private boolean saveAuthTokenToDatabase(SQLiteDatabase db, Account account,
- String type, String authToken) {
- long accountId = getAccountId(db, account);
- if (accountId < 0) {
- return false;
- }
- db.delete(TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
- new String[]{type});
- ContentValues values = new ContentValues();
- values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
- values.put(AUTHTOKENS_TYPE, type);
- values.put(AUTHTOKENS_AUTHTOKEN, authToken);
- return db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0;
- }
-
public String readAuthTokenFromDatabase(Account account, String authTokenType) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
db.beginTransaction();
@@ -436,13 +413,7 @@ public class AccountManagerService extends IAccountManager.Stub {
public String peekAuthToken(Account account, String authTokenType) {
long identityToken = clearCallingIdentity();
try {
- synchronized (mAuthTokenCache) {
- AuthTokenKey key = new AuthTokenKey(account, authTokenType);
- if (mAuthTokenCache.containsKey(key)) {
- return mAuthTokenCache.get(key);
- }
- return readAuthTokenFromDatabase(account, authTokenType);
- }
+ return readAuthTokenFromDatabase(account, authTokenType);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -465,12 +436,16 @@ public class AccountManagerService extends IAccountManager.Stub {
mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
new String[]{account.mName, account.mType});
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+ sendAccountsChangedBroadcast();
} finally {
restoreCallingIdentity(identityToken);
}
}
+ private void sendAccountsChangedBroadcast() {
+ mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+ }
+
public void clearPassword(Account account) {
long identityToken = clearCallingIdentity();
try {
@@ -518,7 +493,7 @@ public class AccountManagerService extends IAccountManager.Stub {
final boolean expectActivityLaunch, final Bundle loginOptions) {
long identityToken = clearCallingIdentity();
try {
- String authToken = getCachedAuthToken(account, authTokenType);
+ String authToken = readAuthTokenFromDatabase(account, authTokenType);
if (authToken != null) {
try {
Bundle result = new Bundle();
@@ -578,19 +553,24 @@ public class AccountManagerService extends IAccountManager.Stub {
}
- public void addAcount(final IAccountManagerResponse response,
- final String accountType, final String authTokenType,
+ public void addAcount(final IAccountManagerResponse response, final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
final boolean expectActivityLaunch, final Bundle options) {
long identityToken = clearCallingIdentity();
try {
new Session(response, accountType, expectActivityLaunch) {
public void run() throws RemoteException {
- mAuthenticator.addAccount(this, mAccountType, authTokenType, options);
+ mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
+ options);
}
protected String toDebugString(long now) {
return super.toDebugString(now) + ", addAccount"
- + ", accountType " + accountType;
+ + ", accountType " + accountType
+ + ", requiredFeatures "
+ + (requiredFeatures != null
+ ? TextUtils.join(",", requiredFeatures)
+ : null);
}
}.bind();
} finally {
@@ -674,24 +654,101 @@ public class AccountManagerService extends IAccountManager.Stub {
}
}
- private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
- synchronized (mAuthTokenCache) {
- if (saveAuthTokenToDatabase(account, authTokenType, authToken)) {
- final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
- mAuthTokenCache.put(key, authToken);
- return true;
- } else {
- return false;
+ private class GetAccountsByTypeAndFeatureSession extends Session {
+ private final String[] mFeatures;
+ private volatile Account[] mAccountsOfType = null;
+ private volatile ArrayList mAccountsWithFeatures = null;
+ private volatile int mCurrentAccount = 0;
+
+ public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
+ String type, String[] features) {
+ super(response, type, false /* expectActivityLaunch */);
+ mFeatures = features;
+ }
+
+ public void run() throws RemoteException {
+ mAccountsOfType = getAccountsByType(mAccountType);
+ // check whether each account matches the requested features
+ mAccountsWithFeatures = new ArrayList(mAccountsOfType.length);
+ mCurrentAccount = 0;
+
+ checkAccount();
+ }
+
+ public void checkAccount() {
+ if (mCurrentAccount >= mAccountsOfType.length) {
+ sendResult();
+ return;
}
+
+ try {
+ mAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
+ } catch (RemoteException e) {
+ onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+ }
+ }
+
+ public void onResult(Bundle result) {
+ mNumResults++;
+ if (result == null) {
+ onError(Constants.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+ return;
+ }
+ if (result.getBoolean(Constants.BOOLEAN_RESULT_KEY, false)) {
+ mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
+ }
+ mCurrentAccount++;
+ checkAccount();
+ }
+
+ public void sendResult() {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ Account[] accounts = new Account[mAccountsWithFeatures.size()];
+ for (int i = 0; i < accounts.length; i++) {
+ accounts[i] = mAccountsWithFeatures.get(i);
+ }
+ Bundle result = new Bundle();
+ result.putParcelableArray(Constants.ACCOUNTS_KEY, accounts);
+ response.onResult(result);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
+ + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+ }
+ }
+ public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
+ String type, String[] features) {
+ if (type == null) {
+ if (response != null) {
+ try {
+ response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS, "type is null");
+ } catch (RemoteException e) {
+ // ignore this
+ }
+ }
+ return;
+ }
+ long identityToken = clearCallingIdentity();
+ try {
+ new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
}
}
- private String getCachedAuthToken(Account account, String authTokenType) {
- synchronized (mAuthTokenCache) {
- final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
- if (!mAuthTokenCache.containsKey(key)) return null;
- return mAuthTokenCache.get(key);
- }
+ private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
+ return saveAuthTokenToDatabase(account, authTokenType, authToken);
}
private long getAccountId(SQLiteDatabase db, Account account) {
@@ -743,7 +800,7 @@ public class AccountManagerService extends IAccountManager.Stub {
final boolean mExpectActivityLaunch;
final long mCreationTime;
- private int mNumResults = 0;
+ public int mNumResults = 0;
private int mNumRequestContinued = 0;
private int mNumErrors = 0;
@@ -754,6 +811,7 @@ public class AccountManagerService extends IAccountManager.Stub {
boolean expectActivityLaunch) {
super();
if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
mResponse = response;
mAccountType = accountType;
mExpectActivityLaunch = expectActivityLaunch;
@@ -1071,7 +1129,7 @@ public class AccountManagerService extends IAccountManager.Stub {
try {
db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+ sendAccountsChangedBroadcast();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
new file mode 100644
index 0000000000000..83377f352f0b1
--- /dev/null
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.accounts;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.view.View;
+import android.util.Log;
+
+public class ChooseAccountActivity extends ListActivity {
+ private static final String TAG = "AccountManager";
+ private Parcelable[] mAccounts = null;
+ private AccountManagerResponse mAccountManagerResponse = null;
+ private Bundle mResult;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ mAccounts = getIntent().getParcelableArrayExtra(Constants.ACCOUNTS_KEY);
+ mAccountManagerResponse =
+ getIntent().getParcelableExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY);
+ } else {
+ mAccounts = savedInstanceState.getParcelableArray(Constants.ACCOUNTS_KEY);
+ mAccountManagerResponse =
+ savedInstanceState.getParcelable(Constants.ACCOUNT_MANAGER_RESPONSE_KEY);
+ }
+
+ String[] mAccountNames = new String[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ mAccountNames[i] = ((Account) mAccounts[i]).mName;
+ }
+
+ // Use an existing ListAdapter that will map an array
+ // of strings to TextViews
+ setListAdapter(new ArrayAdapter(this,
+ android.R.layout.simple_list_item_1, mAccountNames));
+ getListView().setTextFilterEnabled(true);
+ }
+
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Account account = (Account) mAccounts[position];
+ Log.d(TAG, "selected account " + account);
+ Bundle bundle = new Bundle();
+ bundle.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
+ bundle.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
+ mResult = bundle;
+ finish();
+ }
+
+ public void finish() {
+ if (mAccountManagerResponse != null) {
+ if (mResult != null) {
+ mAccountManagerResponse.onResult(mResult);
+ } else {
+ mAccountManagerResponse.onError(Constants.ERROR_CODE_CANCELED, "canceled");
+ }
+ }
+ super.finish();
+ }
+}
diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java
index d3b6aa0f0b5cc..b383c61824739 100644
--- a/core/java/android/accounts/Constants.java
+++ b/core/java/android/accounts/Constants.java
@@ -24,6 +24,7 @@ public class Constants {
public static final int ERROR_CODE_CANCELED = 4;
public static final int ERROR_CODE_INVALID_RESPONSE = 5;
public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
+ public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final String ACCOUNTS_KEY = "accounts";
public static final String AUTHENTICATOR_TYPES_KEY = "authenticator_types";
@@ -37,6 +38,7 @@ public class Constants {
public static final String INTENT_KEY = "intent";
public static final String BOOLEAN_RESULT_KEY = "booleanResult";
public static final String ACCOUNT_AUTHENTICATOR_RESPONSE_KEY = "accountAuthenticatorResponse";
+ public static final String ACCOUNT_MANAGER_RESPONSE_KEY = "accountManagerResponse";
public static final String AUTH_FAILED_MESSAGE_KEY = "authFailedMessage";
/**
* Action sent as a broadcast Intent by the AccountsService
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 70c075208cb73..46a714468fb5a 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -28,7 +28,7 @@ oneway interface IAccountAuthenticator {
* prompts the user for account information and adds the result to the IAccountManager
*/
void addAccount(in IAccountAuthenticatorResponse response, String accountType,
- String authTokenType, in Bundle options);
+ String authTokenType, in String[] requiredFeatures, in Bundle options);
/**
* Checks that the account/password combination is valid.
@@ -58,4 +58,11 @@ oneway interface IAccountAuthenticator {
* launches an activity that lets the user edit and set the properties for an authenticator
*/
void editProperties(in IAccountAuthenticatorResponse response, String accountType);
+
+ /**
+ * returns a Bundle where the boolean value BOOLEAN_RESULT_KEY is set if the account has the
+ * specified features
+ */
+ void hasFeatures(in IAccountAuthenticatorResponse response, in Account account,
+ in String[] features);
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 365a92a05cbb5..5e37a1fd46c2b 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -22,6 +22,7 @@ import android.os.Bundle;
/**
* Central application service that provides account management.
+ * @hide
*/
interface IAccountManager {
String getPassword(in Account account);
@@ -42,16 +43,19 @@ interface IAccountManager {
String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch,
in Bundle options);
void addAcount(in IAccountManagerResponse response, String accountType,
- String authTokenType, boolean expectActivityLaunch, in Bundle options);
+ String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+ in Bundle options);
void updateCredentials(in IAccountManagerResponse response, in Account account,
String authTokenType, boolean expectActivityLaunch, in Bundle options);
void editProperties(in IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch);
void confirmCredentials(in IAccountManagerResponse response, in Account account,
boolean expectActivityLaunch);
+ void getAccountsByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
+ in String[] features);
/*
- * @Deprecated
+ * @deprecated
*/
void confirmPassword(in IAccountManagerResponse response, in Account account,
String password);
diff --git a/core/java/android/accounts/IAccountManagerResponse.aidl b/core/java/android/accounts/IAccountManagerResponse.aidl
index 52f21bc6b322d..ca1203d1a0589 100644
--- a/core/java/android/accounts/IAccountManagerResponse.aidl
+++ b/core/java/android/accounts/IAccountManagerResponse.aidl
@@ -19,6 +19,7 @@ import android.os.Bundle;
/**
* The interface used to return responses for asynchronous calls to the {@link IAccountManager}
+ * @hide
*/
oneway interface IAccountManagerResponse {
void onResult(in Bundle value);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e1a536c3e85c4..c653c7841276c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -983,6 +983,11 @@
android:excludeFromRecents="true">
+
+
+
diff --git a/tests/AndroidTests/src/com/android/unit_tests/accounts/AccountManagerServiceTest.java b/tests/AndroidTests/src/com/android/unit_tests/accounts/AccountManagerServiceTest.java
index 90c0ec0393394..6b8e1f0bc7bc4 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/accounts/AccountManagerServiceTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/accounts/AccountManagerServiceTest.java
@@ -81,7 +81,7 @@ public class AccountManagerServiceTest extends AndroidTestCase {
assertEquals(a22, accounts[4]);
assertEquals(a32, accounts[5]);
- accounts = ams.getAccountsByType("type1");
+ accounts = ams.getAccountsByType("type1" );
Arrays.sort(accounts, new AccountSorter());
assertEquals(3, accounts.length);
assertEquals(a11, accounts[0]);
@@ -90,7 +90,7 @@ public class AccountManagerServiceTest extends AndroidTestCase {
ams.removeAccount(a21);
- accounts = ams.getAccountsByType("type1");
+ accounts = ams.getAccountsByType("type1" );
Arrays.sort(accounts, new AccountSorter());
assertEquals(2, accounts.length);
assertEquals(a11, accounts[0]);
@@ -176,13 +176,6 @@ public class AccountManagerServiceTest extends AndroidTestCase {
assertNull(ams.peekAuthToken(a12, "att2"));
assertEquals("a12_att3", ams.peekAuthToken(a12, "att3"));
- // check that the authtoken cache is consistent with the database
- assertEquals(5, ams.mAuthTokenCache.size());
- for (Map.Entry entry
- : ams.mAuthTokenCache.entrySet()) {
- assertEquals(entry.getValue(), ams.readAuthTokenFromDatabase(
- entry.getKey().mAccount, entry.getKey().mAuthTokenType));
- }
assertNull(ams.readAuthTokenFromDatabase(a12, "att2"));
}
}
\ No newline at end of file