diff options
author | Fred Quintana <fredq@google.com> | 2009-04-20 16:05:10 -0700 |
---|---|---|
committer | Fred Quintana <fredq@google.com> | 2009-04-20 16:06:02 -0700 |
commit | 3326920329cecb57c7ff1fc5c6add5c98aab9ed9 (patch) | |
tree | 13788c9643adef3c0c847d85443ee5696e8186dd /core/java/android/accounts | |
parent | e7c71d3a8cfb0c9c3637e0956fee3abc5a1fb094 (diff) | |
download | frameworks_base-3326920329cecb57c7ff1fc5c6add5c98aab9ed9.zip frameworks_base-3326920329cecb57c7ff1fc5c6add5c98aab9ed9.tar.gz frameworks_base-3326920329cecb57c7ff1fc5c6add5c98aab9ed9.tar.bz2 |
adding concept of features to accounts
Diffstat (limited to 'core/java/android/accounts')
9 files changed, 541 insertions, 112 deletions
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index 587279a..474755c 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -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; } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index c60f15d..007a490 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -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); + } + + 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()); + } } + } - return new AuthenticatorException("unknown error code"); + 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 0000000..25371fd --- /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<AccountManagerResponse> CREATOR = + new Creator<AccountManagerResponse>() { + 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 1f43620..ef0875c 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<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"); } } - } - 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); + 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 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 0000000..83377f3 --- /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<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(); + } +} diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java index d3b6aa0..b383c61 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 70c0752..46a7144 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 365a92a..5e37a1f 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 52f21bc..ca1203d 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); |