Merge change 339

* changes:
  adding concept of features to accounts
This commit is contained in:
Android (Google) Code Review
2009-04-20 18:21:44 -07:00
12 changed files with 711 additions and 1193 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -27,13 +27,13 @@ import android.os.RemoteException;
public abstract class AbstractAccountAuthenticator {
class Transport extends IAccountAuthenticator.Stub {
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
String authTokenType, Bundle options)
String authTokenType, String[] requiredFeatures, Bundle options)
throws RemoteException {
final Bundle result;
try {
result = AbstractAccountAuthenticator.this.addAccount(
new AccountAuthenticatorResponse(response),
accountType, authTokenType, options);
accountType, authTokenType, requiredFeatures, options);
} catch (NetworkErrorException e) {
response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage());
return;
@@ -133,6 +133,25 @@ public abstract class AbstractAccountAuthenticator {
response.onResult(result);
}
}
public void hasFeatures(IAccountAuthenticatorResponse response,
Account account, String[] features) throws RemoteException {
final Bundle result;
try {
result = AbstractAccountAuthenticator.this.hasFeatures(
new AccountAuthenticatorResponse(response), account, features);
} catch (UnsupportedOperationException e) {
response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION,
"hasFeatures not supported");
return;
} catch (NetworkErrorException e) {
response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage());
return;
}
if (result != null) {
response.onResult(result);
}
}
}
Transport mTransport = new Transport();
@@ -160,7 +179,8 @@ public abstract class AbstractAccountAuthenticator {
public abstract Bundle editProperties(AccountAuthenticatorResponse response,
String accountType);
public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
String authTokenType, Bundle options) throws NetworkErrorException;
String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException;
/* @deprecated */
public abstract boolean confirmPassword(AccountAuthenticatorResponse response,
Account account, String password) throws NetworkErrorException;
@@ -171,4 +191,6 @@ public abstract class AbstractAccountAuthenticator {
throws NetworkErrorException;
public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions);
public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features) throws NetworkErrorException;
}

View File

@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.Parcelable;
import java.io.IOException;
import java.util.concurrent.Callable;
@@ -33,7 +34,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
/**
* A class that helps with interactions with the {@link IAccountManager} interface. It provides
* A class that helps with interactions with the AccountManagerService. It provides
* methods to allow for account, password, and authtoken management for all accounts on the
* device. Some of these calls are implemented with the help of the corresponding
* {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling:
@@ -48,6 +49,9 @@ public class AccountManager {
private final Context mContext;
private final IAccountManager mService;
/**
* @hide
*/
public AccountManager(Context context, IAccountManager service) {
mContext = context;
mService = service;
@@ -337,7 +341,7 @@ public class AccountManager {
false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
loginOptions);
}
};
}.start();
}
public Future2 getAuthToken(
@@ -350,18 +354,19 @@ public class AccountManager {
mService.getAuthToken(mResponse, account, authTokenType,
notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
}
};
}.start();
}
public Future2 addAccount(final String accountType,
final String authTokenType, final Bundle addAccountOptions,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, Future2Callback callback, Handler handler) {
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.addAcount(mResponse, accountType, authTokenType,
activity != null, addAccountOptions);
requiredFeatures, activity != null, addAccountOptions);
}
};
}.start();
}
/** @deprecated use {@link #confirmCredentials} instead */
@@ -374,6 +379,33 @@ public class AccountManager {
};
}
public Account[] blockingGetAccountsWithTypeAndFeatures(String type, String[] features)
throws AuthenticatorException, IOException, OperationCanceledException {
Future2 future = getAccountsWithTypeAndFeatures(type, features,
null /* callback */, null /* handler */);
Bundle result = future.getResult();
Parcelable[] accountsTemp = result.getParcelableArray(Constants.ACCOUNTS_KEY);
if (accountsTemp == null) {
throw new AuthenticatorException("accounts should not be null");
}
Account[] accounts = new Account[accountsTemp.length];
for (int i = 0; i < accountsTemp.length; i++) {
accounts[i] = (Account) accountsTemp[i];
}
return accounts;
}
public Future2 getAccountsWithTypeAndFeatures(
final String type, final String[] features,
Future2Callback callback, Handler handler) {
if (type == null) throw new IllegalArgumentException("type is null");
return new AmsTask(null /* activity */, handler, callback) {
public void doWork() throws RemoteException {
mService.getAccountsByTypeAndFeatures(mResponse, type, features);
}
}.start();
}
public Future2 confirmCredentials(final Account account, final Activity activity,
final Future2Callback callback,
final Handler handler) {
@@ -381,7 +413,7 @@ public class AccountManager {
public void doWork() throws RemoteException {
mService.confirmCredentials(mResponse, account, activity != null);
}
};
}.start();
}
public Future2 updateCredentials(final Account account, final String authTokenType,
@@ -393,7 +425,7 @@ public class AccountManager {
mService.updateCredentials(mResponse, account, authTokenType, activity != null,
loginOptions);
}
};
}.start();
}
public Future2 editProperties(final String accountType, final Activity activity,
@@ -403,7 +435,7 @@ public class AccountManager {
public void doWork() throws RemoteException {
mService.editProperties(mResponse, accountType, activity != null);
}
};
}.start();
}
private void ensureNotOnMainThread() {
@@ -502,11 +534,12 @@ public class AccountManager {
}
}
public abstract class AmsTask extends FutureTask<Bundle> implements Future2 {
private abstract class AmsTask extends FutureTask<Bundle> 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<Bundle>() {
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<Boolean> implements Future1<Boolean> {
private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> {
final IAccountManagerResponse response;
final Handler mHandler;
final Future1Callback<Boolean> 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();
}
}

View File

@@ -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<AccountManagerResponse> CREATOR =
new Creator<AccountManagerResponse>() {
public AccountManagerResponse createFromParcel(Parcel source) {
return new AccountManagerResponse(source);
}
public AccountManagerResponse[] newArray(int size) {
return new AccountManagerResponse[size];
}
};
}

View File

@@ -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<AuthTokenKey, String> 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<AuthTokenKey> 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<Account> 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<Account>(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();

View File

@@ -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<String>(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();
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -983,6 +983,11 @@
android:excludeFromRecents="true">
</activity>
<activity android:name="android.accounts.ChooseAccountActivity"
android:excludeFromRecents="true"
android:exported="true">
</activity>
<provider android:name=".content.SyncProvider"
android:authorities="sync" android:multiprocess="false" />

View File

@@ -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<AccountManagerService.AuthTokenKey, String> entry
: ams.mAuthTokenCache.entrySet()) {
assertEquals(entry.getValue(), ams.readAuthTokenFromDatabase(
entry.getKey().mAccount, entry.getKey().mAuthTokenType));
}
assertNull(ams.readAuthTokenFromDatabase(a12, "att2"));
}
}