diff options
author | Fred Quintana <fredq@google.com> | 2010-12-02 14:20:51 -0800 |
---|---|---|
committer | Fred Quintana <fredq@google.com> | 2010-12-03 15:35:48 -0800 |
commit | 56285a60e83138bb4b4f2d3bdec91b2f3ca11aa2 (patch) | |
tree | e2108335bb11790bbc9fb67fa76b01aa9e0b7992 /core/java/android/accounts | |
parent | cd5e8b60cad508714fc9ecf42d6431ad8ccf7db1 (diff) | |
download | frameworks_base-56285a60e83138bb4b4f2d3bdec91b2f3ca11aa2.zip frameworks_base-56285a60e83138bb4b4f2d3bdec91b2f3ca11aa2.tar.gz frameworks_base-56285a60e83138bb4b4f2d3bdec91b2f3ca11aa2.tar.bz2 |
add caching to the AccountManagerService
- cache the accounts, userdata and authtokens
- make the AccountManagerServiceTest work again
- add a log statement for every binder call
http://b/issue?id=3188457
Change-Id: I96b94b9b690cf391fe4341e2a72893a6d823777b
Diffstat (limited to 'core/java/android/accounts')
3 files changed, 490 insertions, 182 deletions
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java index d2b3bc7..524d3f4 100644 --- a/core/java/android/accounts/AccountAuthenticatorCache.java +++ b/core/java/android/accounts/AccountAuthenticatorCache.java @@ -38,7 +38,8 @@ import java.io.IOException; * @hide */ /* package private */ class AccountAuthenticatorCache - extends RegisteredServicesCache<AuthenticatorDescription> { + extends RegisteredServicesCache<AuthenticatorDescription> + implements IAccountAuthenticatorCache { private static final String TAG = "Account"; private static final MySerializer sSerializer = new MySerializer(); diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 8471df9..61ef8f2 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -16,6 +16,10 @@ package android.accounts; +import com.android.internal.R; +import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.TelephonyIntents; + import android.Manifest; import android.app.Notification; import android.app.NotificationManager; @@ -57,16 +61,13 @@ import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import com.android.internal.R; -import com.android.internal.telephony.ITelephony; -import com.android.internal.telephony.TelephonyIntents; - /** * A system service that provides account, password, and authtoken management for all * accounts on the device. Some of these calls are implemented with the help of the corresponding @@ -91,13 +92,15 @@ public class AccountManagerService private final Context mContext; + private final PackageManager mPackageManager; + private HandlerThread mMessageThread; private final MessageHandler mMessageHandler; // Messages that can be sent on mHandler private static final int MESSAGE_TIMED_OUT = 3; - private final AccountAuthenticatorCache mAuthenticatorCache; + private final IAccountAuthenticatorCache mAuthenticatorCache; private final DatabaseHelper mOpenHelper; private final SimWatcher mSimWatcher; @@ -129,8 +132,6 @@ public class AccountManagerService private static final String META_KEY = "key"; private static final String META_VALUE = "value"; - private static final String[] ACCOUNT_NAME_TYPE_PROJECTION = - new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE}; private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION = new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; private static final Intent ACCOUNTS_CHANGED_INTENT; @@ -143,6 +144,15 @@ public class AccountManagerService + " AND " + ACCOUNTS_NAME + "=?" + " AND " + ACCOUNTS_TYPE + "=?"; + private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = + AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE, + AUTHTOKENS_AUTHTOKEN}; + + private static final String SELECTION_USERDATA_BY_ACCOUNT = + EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE}; + private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>(); private final AtomicInteger mNotificationIds = new AtomicInteger(1); @@ -163,6 +173,16 @@ public class AccountManagerService ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); } + private final Object mCacheLock = new Object(); + /** protected by the {@link #mCacheLock} */ + private final HashMap<String, Account[]> mAccountCache = new HashMap<String, Account[]>(); + /** protected by the {@link #mCacheLock} */ + private HashMap<Account, HashMap<String, String>> mUserDataCache = + new HashMap<Account, HashMap<String, String>>(); + /** protected by the {@link #mCacheLock} */ + private HashMap<Account, HashMap<String, String>> mAuthTokenCache = + new HashMap<Account, HashMap<String, String>>(); + /** * This should only be called by system code. One should only call this after the service * has started. @@ -173,47 +193,14 @@ public class AccountManagerService return sThis.get(); } - public class AuthTokenKey { - public final Account mAccount; - public final String mAuthTokenType; - private final int mHashCode; - - public AuthTokenKey(Account account, String authTokenType) { - mAccount = account; - mAuthTokenType = authTokenType; - mHashCode = computeHashCode(); - } - - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof AuthTokenKey)) { - return false; - } - AuthTokenKey other = (AuthTokenKey)o; - if (!mAccount.equals(other.mAccount)) { - return false; - } - return (mAuthTokenType == null) - ? other.mAuthTokenType == null - : mAuthTokenType.equals(other.mAuthTokenType); - } - - private int computeHashCode() { - int result = 17; - result = 31 * result + mAccount.hashCode(); - result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode()); - return result; - } - - public int hashCode() { - return mHashCode; - } + public AccountManagerService(Context context) { + this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); } - public AccountManagerService(Context context) { + public AccountManagerService(Context context, PackageManager packageManager, + IAccountAuthenticatorCache authenticatorCache) { mContext = context; + mPackageManager = packageManager; mOpenHelper = new DatabaseHelper(mContext); @@ -221,32 +208,58 @@ public class AccountManagerService mMessageThread.start(); mMessageHandler = new MessageHandler(mMessageThread.getLooper()); - mAuthenticatorCache = new AccountAuthenticatorCache(mContext); + mAuthenticatorCache = authenticatorCache; mAuthenticatorCache.setListener(this, null /* Handler */); mSimWatcher = new SimWatcher(mContext); sThis.set(this); - validateAccounts(); + validateAccountsAndPopulateCache(); } - private void validateAccounts() { + private void validateAccountsAndPopulateCache() { boolean accountDeleted = false; SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, null, null, null, null, null); try { - while (cursor.moveToNext()) { - final long accountId = cursor.getLong(0); - final String accountType = cursor.getString(1); - final String accountName = cursor.getString(2); - if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType)) - == null) { - Log.d(TAG, "deleting account " + accountName + " because type " - + accountType + " no longer has a registered authenticator"); - db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); - accountDeleted = true; + synchronized (mCacheLock) { + mAccountCache.clear(); + final HashMap<String, ArrayList<String>> accountNamesByType = + new HashMap<String, ArrayList<String>>(); + while (cursor.moveToNext()) { + final long accountId = cursor.getLong(0); + final String accountType = cursor.getString(1); + final String accountName = cursor.getString(2); + if (mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(accountType)) == null) { + Log.d(TAG, "deleting account " + accountName + " because type " + + accountType + " no longer has a registered authenticator"); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); + accountDeleted = true; + final Account account = new Account(accountName, accountType); + mUserDataCache.remove(account); + mAuthTokenCache.remove(account); + } else { + ArrayList<String> accountNames = accountNamesByType.get(accountType); + if (accountNames == null) { + accountNames = new ArrayList<String>(); + accountNamesByType.put(accountType, accountNames); + } + accountNames.add(accountName); + } + } + for (HashMap.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) { + final String accountType = cur.getKey(); + final ArrayList<String> accountNames = cur.getValue(); + final Account[] accountsForType = new Account[accountNames.size()]; + int i = 0; + for (String accountName : accountNames) { + accountsForType[i] = new Account(accountName, accountType); + ++i; + } + mAccountCache.put(accountType, accountsForType); } } } finally { @@ -258,30 +271,15 @@ public class AccountManagerService } public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { - boolean accountDeleted = false; - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - Cursor cursor = db.query(TABLE_ACCOUNTS, - new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, - ACCOUNTS_TYPE + "=?", new String[]{desc.type}, null, null, null); - try { - while (cursor.moveToNext()) { - final long accountId = cursor.getLong(0); - final String accountType = cursor.getString(1); - final String accountName = cursor.getString(2); - Log.d(TAG, "deleting account " + accountName + " because type " - + accountType + " no longer has a registered authenticator"); - db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); - accountDeleted = true; - } - } finally { - cursor.close(); - if (accountDeleted) { - sendAccountsChangedBroadcast(); - } - } + validateAccountsAndPopulateCache(); } public String getPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); @@ -313,39 +311,29 @@ public class AccountManagerService } public String getUserData(Account account, String key) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); if (key == null) throw new IllegalArgumentException("key is null"); checkAuthenticateAccountsPermission(account); long identityToken = clearCallingIdentity(); try { - return readUserDataFromDatabase(account, key); + return readUserDataFromCache(account, key); } finally { restoreCallingIdentity(identityToken); } } - private String readUserDataFromDatabase(Account account, String key) { - if (account == null) { - return null; - } - - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE}, - EXTRAS_ACCOUNTS_ID - + "=(select _id FROM accounts WHERE name=? AND type=?) AND " - + EXTRAS_KEY + "=?", - new String[]{account.name, account.type, key}, null, null, null); - try { - if (cursor.moveToNext()) { - return cursor.getString(0); - } - return null; - } finally { - cursor.close(); - } - } - public AuthenticatorDescription[] getAuthenticatorTypes() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthenticatorTypes: " + + "caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } long identityToken = clearCallingIdentity(); try { Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>> @@ -364,27 +352,12 @@ public class AccountManagerService } } - public Account[] getAccountsByType(String accountType) { - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - - final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?"); - final String[] selectionArgs = accountType == null ? null : new String[]{accountType}; - Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION, - selection, selectionArgs, null, null, null); - try { - int i = 0; - Account[] accounts = new Account[cursor.getCount()]; - while (cursor.moveToNext()) { - accounts[i] = new Account(cursor.getString(1), cursor.getString(2)); - i++; - } - return accounts; - } finally { - cursor.close(); - } - } - public boolean addAccount(Account account, String password, Bundle extras) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); @@ -417,6 +390,8 @@ public class AccountManagerService + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", new String[]{account.name, account.type}); if (numMatches > 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since the account already exists"); return false; } ContentValues values = new ContentValues(); @@ -425,17 +400,22 @@ public class AccountManagerService values.put(ACCOUNTS_PASSWORD, password); long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); if (accountId < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping the DB insert failed"); return false; } if (extras != null) { for (String key : extras.keySet()) { final String value = extras.getString(key); if (insertExtra(db, accountId, key, value) < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since insertExtra failed for key " + key); return false; } } } db.setTransactionSuccessful(); + insertAccountIntoCache(account); } finally { db.endTransaction(); } @@ -455,6 +435,13 @@ public class AccountManagerService public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "hasFeatures: " + account + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); if (features == null) throw new IllegalArgumentException("features is null"); @@ -495,6 +482,10 @@ public class AccountManagerService onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); return; } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } final Bundle newResult = new Bundle(); newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)); @@ -516,6 +507,12 @@ public class AccountManagerService } public void removeAccount(IAccountManagerResponse response, Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccount: " + account + + ", response " + response + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); @@ -565,6 +562,10 @@ public class AccountManagerService } IAccountManagerResponse response = getResponseAndClose(); if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } Bundle result2 = new Bundle(); result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed); try { @@ -578,14 +579,20 @@ public class AccountManagerService } } - private void removeAccount(Account account) { + protected void removeAccount(Account account) { final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", new String[]{account.name, account.type}); + removeAccountFromCache(account); sendAccountsChangedBroadcast(); } public void invalidateAuthToken(String accountType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "invalidateAuthToken: accountType " + accountType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (accountType == null) throw new IllegalArgumentException("accountType is null"); if (authToken == null) throw new IllegalArgumentException("authToken is null"); checkManageAccountsOrUseCredentialsPermissions(); @@ -625,6 +632,7 @@ public class AccountManagerService String accountName = cursor.getString(1); String authTokenType = cursor.getString(2); db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); + writeAuthTokenIntoCache(new Account(accountName, accountType), authTokenType, null); } } finally { cursor.close(); @@ -652,6 +660,7 @@ public class AccountManagerService values.put(AUTHTOKENS_AUTHTOKEN, authToken); if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { db.setTransactionSuccessful(); + writeAuthTokenIntoCache(account, type, authToken); return true; } return false; @@ -660,39 +669,31 @@ public class AccountManagerService } } - public String readAuthTokenFromDatabase(Account account, String authTokenType) { - if (account == null || authTokenType == null) { - return null; - } - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN}, - AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?) AND " - + AUTHTOKENS_TYPE + "=?", - new String[]{account.name, account.type, authTokenType}, - null, null, null); - try { - if (cursor.moveToNext()) { - return cursor.getString(0); - } - return null; - } finally { - cursor.close(); - } - } - public String peekAuthToken(Account account, String authTokenType) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "peekAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); checkAuthenticateAccountsPermission(account); long identityToken = clearCallingIdentity(); try { - return readAuthTokenFromDatabase(account, authTokenType); + return readAuthTokenFromCache(account, authTokenType); } finally { restoreCallingIdentity(identityToken); } } public void setAuthToken(Account account, String authTokenType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); checkAuthenticateAccountsPermission(account); @@ -705,6 +706,11 @@ public class AccountManagerService } public void setPassword(Account account, String password) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); long identityToken = clearCallingIdentity(); @@ -738,10 +744,17 @@ public class AccountManagerService } private void sendAccountsChangedBroadcast() { + Log.i(TAG, "the accounts changed, sending broadcast of " + + ACCOUNTS_CHANGED_INTENT.getAction()); mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); } public void clearPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "clearPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); long identityToken = clearCallingIdentity(); @@ -753,13 +766,16 @@ public class AccountManagerService } public void setUserData(Account account, String key, String value) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (key == null) throw new IllegalArgumentException("key is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); long identityToken = clearCallingIdentity(); - if (account == null) { - return; - } if (account.type.equals(GOOGLE_ACCOUNT_TYPE) && key.equals("broadcast")) { sendAccountsChangedBroadcast(); return; @@ -797,12 +813,20 @@ public class AccountManagerService } db.setTransactionSuccessful(); + writeUserDataIntoCache(account, key, value); } finally { db.endTransaction(); } } private void onResult(IAccountManagerResponse response, Bundle result) { + if (result == null) { + Log.e(TAG, "the result is unexpectedly null", new Exception()); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } try { response.onResult(result); } catch (RemoteException e) { @@ -817,6 +841,15 @@ public class AccountManagerService public void getAuthToken(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean notifyOnAuthFailure, final boolean expectActivityLaunch, final Bundle loginOptions) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthToken: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", notifyOnAuthFailure " + notifyOnAuthFailure + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); @@ -829,7 +862,7 @@ public class AccountManagerService // if the caller has permission, do the peek. otherwise go the more expensive // route of starting a Session if (permissionGranted) { - String authToken = readAuthTokenFromDatabase(account, authTokenType); + String authToken = readAuthTokenFromCache(account, authTokenType); if (authToken != null) { Bundle result = new Bundle(); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); @@ -920,8 +953,7 @@ public class AccountManagerService n.setLatestEventInfo(mContext, title, subtitle, PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(getCredentialPermissionNotificationId(account, authTokenType, uid), n); + installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n); } private Intent newGrantCredentialsPermissionIntent(Account account, int uid, @@ -952,7 +984,7 @@ public class AccountManagerService intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT_TYPE_LABEL, authContext.getString(serviceInfo.type.labelId)); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_PACKAGES, - mContext.getPackageManager().getPackagesForUid(uid)); + mPackageManager.getPackagesForUid(uid)); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid); return intent; } @@ -985,10 +1017,18 @@ public class AccountManagerService return id; } - public void addAcount(final IAccountManagerResponse response, final String accountType, final String authTokenType, final String[] requiredFeatures, final boolean expectActivityLaunch, final Bundle options) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: accountType " + accountType + + ", response " + response + + ", authTokenType " + authTokenType + + ", requiredFeatures " + stringArrayToString(requiredFeatures) + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (accountType == null) throw new IllegalArgumentException("accountType is null"); checkManageAccountsPermission(); @@ -1017,6 +1057,13 @@ public class AccountManagerService public void confirmCredentials(IAccountManagerResponse response, final Account account, final Bundle options, final boolean expectActivityLaunch) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "confirmCredentials: " + account + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); @@ -1040,6 +1087,14 @@ public class AccountManagerService public void updateCredentials(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean expectActivityLaunch, final Bundle loginOptions) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "updateCredentials: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); @@ -1066,6 +1121,13 @@ public class AccountManagerService public void editProperties(IAccountManagerResponse response, final String accountType, final boolean expectActivityLaunch) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "editProperties: accountType " + accountType + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (accountType == null) throw new IllegalArgumentException("accountType is null"); checkManageAccountsPermission(); @@ -1100,7 +1162,7 @@ public class AccountManagerService } public void run() throws RemoteException { - mAccountsOfType = getAccountsByType(mAccountType); + mAccountsOfType = getAccountsByTypeFromCache(mAccountType); // check whether each account matches the requested features mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length); mCurrentAccount = 0; @@ -1154,6 +1216,10 @@ public class AccountManagerService for (int i = 0; i < accounts.length; i++) { accounts[i] = mAccountsWithFeatures.get(i); } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } Bundle result = new Bundle(); result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); response.onResult(result); @@ -1174,10 +1240,15 @@ public class AccountManagerService } public Account[] getAccounts(String type) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } checkReadAccountsPermission(); long identityToken = clearCallingIdentity(); try { - return getAccountsByType(type); + return getAccountsByTypeFromCache(type); } finally { restoreCallingIdentity(identityToken); } @@ -1185,23 +1256,20 @@ public class AccountManagerService public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } if (response == null) throw new IllegalArgumentException("response is null"); if (type == null) throw new IllegalArgumentException("accountType is null"); checkReadAccountsPermission(); - if (features != null && type == null) { - if (response != null) { - try { - response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "type is null"); - } catch (RemoteException e) { - // ignore this - } - } - return; - } long identityToken = clearCallingIdentity(); try { if (features == null || features.length == 0) { - Account[] accounts = getAccountsByType(type); + Account[] accounts = getAccountsByTypeFromCache(type); Bundle result = new Bundle(); result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); onResult(response, result); @@ -1397,12 +1465,20 @@ public class AccountManagerService if (response != null) { try { if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); + } response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle returned"); } else { if (mStripAuthTokenFromResult) { result.remove(AccountManager.KEY_AUTHTOKEN); } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onResult() on response " + response); + } response.onResult(result); } } catch (RemoteException e) { @@ -1420,13 +1496,11 @@ public class AccountManagerService public void onError(int errorCode, String errorMessage) { mNumErrors++; - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Session.onError: " + errorCode + ", " + errorMessage); - } IAccountManagerResponse response = getResponseAndClose(); if (response != null) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Session.onError: responding"); + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); } try { response.onError(errorCode, errorMessage); @@ -1744,7 +1818,7 @@ public class AccountManagerService } } } else { - Account[] accounts = getAccountsByType(null /* type */); + Account[] accounts = getAccountsByTypeFromCache(null /* type */); fout.println("Accounts: " + accounts.length); for (Account account : accounts) { fout.println(" " + account); @@ -1786,15 +1860,19 @@ public class AccountManagerService String.format(notificationTitleFormat, account.name), message, PendingIntent.getActivity( mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(notificationId, n); + installNotification(notificationId, n); } } finally { restoreCallingIdentity(identityToken); } } - private void cancelNotification(int id) { + protected void installNotification(final int notificationId, final Notification n) { + ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(notificationId, n); + } + + protected void cancelNotification(int id) { long identityToken = clearCallingIdentity(); try { ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) @@ -1811,24 +1889,24 @@ public class AccountManagerService for (String perm : permissions) { if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "caller uid " + uid + " has " + perm); + Log.v(TAG, " caller uid " + uid + " has " + perm); } return; } } String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions); - Log.w(TAG, msg); + Log.w(TAG, " " + msg); throw new SecurityException(msg); } private boolean inSystemImage(int callerUid) { - String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); + String[] packages = mPackageManager.getPackagesForUid(callerUid); for (String name : packages) { try { - PackageInfo packageInfo = - mContext.getPackageManager().getPackageInfo(name, 0 /* flags */); - if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */); + if (packageInfo != null + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { return true; } } catch (PackageManager.NameNotFoundException e) { @@ -1846,7 +1924,7 @@ public class AccountManagerService && hasExplicitlyGrantedPermission(account, authTokenType); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " - + callerUid + ", account " + account + + callerUid + ", " + account + ": is authenticator? " + fromAuthenticator + ", has explicit permission? " + hasExplicitGrants); } @@ -1858,7 +1936,7 @@ public class AccountManagerService mAuthenticatorCache.getAllServices()) { if (serviceInfo.type.type.equals(accountType)) { return (serviceInfo.uid == callingUid) || - (mContext.getPackageManager().checkSignatures(serviceInfo.uid, callingUid) + (mPackageManager.checkSignatures(serviceInfo.uid, callingUid) == PackageManager.SIGNATURE_MATCH); } } @@ -1975,4 +2053,170 @@ public class AccountManagerService } cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); } + + static final private String stringArrayToString(String[] value) { + return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; + } + + private void removeAccountFromCache(Account account) { + synchronized (mCacheLock) { + final Account[] oldAccountsForType = mAccountCache.get(account.type); + if (oldAccountsForType != null) { + ArrayList<Account> newAccountsList = new ArrayList<Account>(); + for (Account curAccount : oldAccountsForType) { + if (!curAccount.equals(account)) { + newAccountsList.add(curAccount); + } + } + if (newAccountsList.isEmpty()) { + mAccountCache.remove(account.type); + } else { + Account[] newAccountsForType = new Account[newAccountsList.size()]; + newAccountsForType = newAccountsList.toArray(newAccountsForType); + mAccountCache.put(account.type, newAccountsForType); + } + } + mUserDataCache.remove(account); + mAuthTokenCache.remove(account); + } + } + + /** + * This assumes that the caller has already checked that the account is not already present. + */ + private void insertAccountIntoCache(Account account) { + synchronized (mCacheLock) { + Account[] accountsForType = mAccountCache.get(account.type); + int oldLength = (accountsForType != null) ? accountsForType.length : 0; + Account[] newAccountsForType = new Account[oldLength + 1]; + if (accountsForType != null) { + System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); + } + newAccountsForType[oldLength] = account; + mAccountCache.put(account.type, newAccountsForType); + } + } + + protected Account[] getAccountsByTypeFromCache(String accountType) { + synchronized (mCacheLock) { + if (accountType != null) { + final Account[] accounts = mAccountCache.get(accountType); + if (accounts == null) { + return EMPTY_ACCOUNT_ARRAY; + } else { + return Arrays.copyOf(accounts, accounts.length); + } + } else { + int totalLength = 0; + for (Account[] accounts : mAccountCache.values()) { + totalLength += accounts.length; + } + if (totalLength == 0) { + return EMPTY_ACCOUNT_ARRAY; + } + Account[] accounts = new Account[totalLength]; + totalLength = 0; + for (Account[] accountsOfType : mAccountCache.values()) { + System.arraycopy(accountsOfType, 0, accounts, totalLength, + accountsOfType.length); + totalLength += accountsOfType.length; + } + return accounts; + } + } + } + + protected void writeUserDataIntoCache(Account account, String key, String value) { + synchronized (mCacheLock) { + HashMap<String, String> userDataForAccount = mUserDataCache.get(account); + if (userDataForAccount == null) { + userDataForAccount = readUserDataForAccountFromDatabase(account); + mUserDataCache.put(account, userDataForAccount); + } + if (value == null) { + userDataForAccount.remove(key); + } else { + userDataForAccount.put(key, value); + } + } + } + + protected void writeAuthTokenIntoCache(Account account, String key, String value) { + synchronized (mCacheLock) { + HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); + if (authTokensForAccount == null) { + authTokensForAccount = readAuthTokensForAccountFromDatabase(account); + mAuthTokenCache.put(account, authTokensForAccount); + } + if (value == null) { + authTokensForAccount.remove(key); + } else { + authTokensForAccount.put(key, value); + } + } + } + + protected String readAuthTokenFromCache(Account account, String authTokenType) { + synchronized (mCacheLock) { + HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); + if (authTokensForAccount == null) { + // need to populate the cache for this account + authTokensForAccount = readAuthTokensForAccountFromDatabase(account); + mAuthTokenCache.put(account, authTokensForAccount); + } + return authTokensForAccount.get(authTokenType); + } + } + + protected String readUserDataFromCache(Account account, String key) { + synchronized (mCacheLock) { + HashMap<String, String> userDataForAccount = mUserDataCache.get(account); + if (userDataForAccount == null) { + // need to populate the cache for this account + userDataForAccount = readUserDataForAccountFromDatabase(account); + mUserDataCache.put(account, userDataForAccount); + } + return userDataForAccount.get(key); + } + } + + protected HashMap<String, String> readUserDataForAccountFromDatabase(Account account) { + HashMap<String, String> userDataForAccount = new HashMap<String, String>(); + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_EXTRAS, + COLUMNS_EXTRAS_KEY_AND_VALUE, + SELECTION_USERDATA_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String tmpkey = cursor.getString(0); + final String value = cursor.getString(1); + userDataForAccount.put(tmpkey, value); + } + } finally { + cursor.close(); + } + return userDataForAccount; + } + + protected HashMap<String, String> readAuthTokensForAccountFromDatabase(Account account) { + HashMap<String, String> authTokensForAccount = new HashMap<String, String>(); + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_AUTHTOKENS, + COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, + SELECTION_AUTHTOKENS_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String type = cursor.getString(0); + final String authToken = cursor.getString(1); + authTokensForAccount.put(type, authToken); + } + } finally { + cursor.close(); + } + return authTokensForAccount; + } } diff --git a/core/java/android/accounts/IAccountAuthenticatorCache.java b/core/java/android/accounts/IAccountAuthenticatorCache.java new file mode 100644 index 0000000..618771f --- /dev/null +++ b/core/java/android/accounts/IAccountAuthenticatorCache.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 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.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.os.Handler; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collection; + +/** + * An interface to the Authenticator specialization of RegisteredServicesCache. The use of + * this interface by the AccountManagerService makes it easier to unit test it. + * @hide + */ +public interface IAccountAuthenticatorCache { + /** + * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matched the specified {@link android.accounts.AuthenticatorDescription} or null + * if none match. + * @param type the authenticator type to return + * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matches the account type or null if none is present + */ + RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo( + AuthenticatorDescription type); + + /** + * @return A copy of a Collection of all the current Authenticators. + */ + Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices(); + + /** + * Dumps the state of the cache. See + * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])} + */ + void dump(FileDescriptor fd, PrintWriter fout, String[] args); + + /** + * Sets a listener that will be notified whenever the authenticator set changes + * @param listener the listener to notify, or null + * @param handler the {@link Handler} on which the notification will be posted. If null + * the notification will be posted on the main thread. + */ + void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener, + Handler handler); +}
\ No newline at end of file |