Merge "Allow authenticators to rename accounts." into lmp-dev

This commit is contained in:
Carlos Valdivia
2014-07-23 00:33:51 +00:00
committed by Android (Google) Code Review
4 changed files with 286 additions and 1 deletions

View File

@@ -2746,6 +2746,7 @@ package android.accounts {
method public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthTokenByFeatures(java.lang.String, java.lang.String, java.lang.String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AuthenticatorDescription[] getAuthenticatorTypes();
method public java.lang.String getPassword(android.accounts.Account);
method public java.lang.String getPreviousName(android.accounts.Account);
method public java.lang.String getUserData(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, java.lang.String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void invalidateAuthToken(java.lang.String, java.lang.String);
@@ -2753,6 +2754,7 @@ package android.accounts {
method public java.lang.String peekAuthToken(android.accounts.Account, java.lang.String);
method public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
method public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);

View File

@@ -662,6 +662,72 @@ public class AccountManager {
}
}
/**
* Rename the specified {@link Account}. This is equivalent to removing
* the existing account and adding a new renamed account with the old
* account's user data.
*
* <p>It is safe to call this method from the main thread.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
* and have the same UID as the account's authenticator.
*
* @param account The {@link Account} to rename
* @param newName String name to be associated with the account.
* @param callback Callback to invoke when the request completes, null for
* no callback
* @param handler {@link Handler} identifying the callback thread, null for
* the main thread
* @return An {@link AccountManagerFuture} which resolves to the Account
* after the name change. If successful the account's name will be the
* specified new name.
*/
public AccountManagerFuture<Account> renameAccount(
final Account account,
final String newName,
AccountManagerCallback<Account> callback,
Handler handler) {
if (account == null) throw new IllegalArgumentException("account is null.");
if (TextUtils.isEmpty(newName)) {
throw new IllegalArgumentException("newName is empty or null.");
}
return new Future2Task<Account>(handler, callback) {
@Override
public void doWork() throws RemoteException {
mService.renameAccount(mResponse, account, newName);
}
@Override
public Account bundleToResult(Bundle bundle) throws AuthenticatorException {
String name = bundle.getString(KEY_ACCOUNT_NAME);
String type = bundle.getString(KEY_ACCOUNT_TYPE);
return new Account(name, type);
}
}.start();
}
/**
* Gets the previous name associated with the account or {@code null}, if
* none. This is intended so that clients of {@link
* #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
* authenticator has renamed an account.
*
* <p>It is safe to call this method from the main thread.
*
* @param account The account to query for a previous name.
* @return The account's previous name, null if the account has never been
* renamed.
*/
public String getPreviousName(final Account account) {
if (account == null) throw new IllegalArgumentException("account is null");
try {
return mService.getPreviousName(account);
} catch (RemoteException e) {
// will never happen
throw new RuntimeException(e);
}
}
/**
* Removes an account from the AccountManager. Does nothing if the account
* does not exist. Does not delete the account from the server.

View File

@@ -69,4 +69,10 @@ interface IAccountManager {
boolean addSharedAccountAsUser(in Account account, int userId);
Account[] getSharedAccountsAsUser(int userId);
boolean removeSharedAccountAsUser(in Account account, int userId);
/* Account renaming. */
void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
String getPreviousName(in Account account);
boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
}

View File

@@ -77,6 +77,7 @@ import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.FgThread;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
@@ -109,7 +110,7 @@ public class AccountManagerService
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
private static final String DATABASE_NAME = "accounts.db";
private static final int DATABASE_VERSION = 5;
private static final int DATABASE_VERSION = 6;
private final Context mContext;
@@ -130,6 +131,7 @@ public class AccountManagerService
private static final String ACCOUNTS_TYPE = "type";
private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
private static final String ACCOUNTS_PASSWORD = "password";
private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
private static final String TABLE_AUTHTOKENS = "authtokens";
private static final String AUTHTOKENS_ID = "_id";
@@ -196,6 +198,20 @@ public class AccountManagerService
/** protected by the {@link #cacheLock} */
private final HashMap<Account, HashMap<String, String>> authTokenCache =
new HashMap<Account, HashMap<String, String>>();
/**
* protected by the {@link #cacheLock}
*
* Caches the previous names associated with an account. Previous names
* should be cached because we expect that when an Account is renamed,
* many clients will receive a LOGIN_ACCOUNTS_CHANGED broadcast and
* want to know if the accounts they care about have been renamed.
*
* The previous names are wrapped in an {@link AtomicReference} so that
* we can distinguish between those accounts with no previous names and
* those whose previous names haven't been cached (yet).
*/
private final HashMap<Account, AtomicReference<String>> previousNameCache =
new HashMap<Account, AtomicReference<String>>();
UserAccounts(Context context, int userId) {
this.userId = userId;
@@ -516,6 +532,57 @@ public class AccountManagerService
}
}
@Override
public String getPreviousName(Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getPreviousName: " + account
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
UserAccounts accounts = getUserAccountsForCaller();
long identityToken = clearCallingIdentity();
try {
return readPreviousNameInternal(accounts, account);
} finally {
restoreCallingIdentity(identityToken);
}
}
private String readPreviousNameInternal(UserAccounts accounts, Account account) {
if (account == null) {
return null;
}
synchronized (accounts.cacheLock) {
AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
if (previousNameRef == null) {
final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
Cursor cursor = db.query(
TABLE_ACCOUNTS,
new String[]{ ACCOUNTS_PREVIOUS_NAME },
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
new String[] { account.name, account.type },
null,
null,
null);
try {
if (cursor.moveToNext()) {
String previousName = cursor.getString(0);
previousNameRef = new AtomicReference<String>(previousName);
accounts.previousNameCache.put(account, previousNameRef);
return previousName;
} else {
return null;
}
} finally {
cursor.close();
}
} else {
return previousNameRef.get();
}
}
}
@Override
public String getUserData(Account account, String key) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -858,6 +925,119 @@ public class AccountManagerService
}
}
@Override
public void renameAccount(
IAccountManagerResponse response, Account accountToRename, String newName) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "renameAccount: " + accountToRename + " -> " + newName
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
if (accountToRename == null) throw new IllegalArgumentException("account is null");
checkAuthenticateAccountsPermission(accountToRename);
UserAccounts accounts = getUserAccountsForCaller();
long identityToken = clearCallingIdentity();
try {
Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type);
try {
response.onResult(result);
} catch (RemoteException e) {
Log.w(TAG, e.getMessage());
}
} finally {
restoreCallingIdentity(identityToken);
}
}
private Account renameAccountInternal(
UserAccounts accounts, Account accountToRename, String newName) {
Account resultAccount = null;
/*
* Cancel existing notifications. Let authenticators
* re-post notifications as required. But we don't know if
* the authenticators have bound their notifications to
* now stale account name data.
*
* With a rename api, we might not need to do this anymore but it
* shouldn't hurt.
*/
cancelNotification(
getSigninRequiredNotificationId(accounts, accountToRename),
new UserHandle(accounts.userId));
synchronized(accounts.credentialsPermissionNotificationIds) {
for (Pair<Pair<Account, String>, Integer> pair:
accounts.credentialsPermissionNotificationIds.keySet()) {
if (accountToRename.equals(pair.first.first)) {
int id = accounts.credentialsPermissionNotificationIds.get(pair);
cancelNotification(id, new UserHandle(accounts.userId));
}
}
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
db.beginTransaction();
boolean isSuccessful = false;
Account renamedAccount = new Account(newName, accountToRename.type);
try {
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_NAME, newName);
values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name);
final long accountId = getAccountIdLocked(db, accountToRename);
if (accountId >= 0) {
final String[] argsAccountId = { String.valueOf(accountId) };
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
db.setTransactionSuccessful();
isSuccessful = true;
}
} finally {
db.endTransaction();
if (isSuccessful) {
/*
* Database transaction was successful. Clean up cached
* data associated with the account in the user profile.
*/
insertAccountIntoCacheLocked(accounts, renamedAccount);
/*
* Extract the data and token caches before removing the
* old account to preserve the user data associated with
* the account.
*/
HashMap<String, String> tmpData = accounts.userDataCache.get(accountToRename);
HashMap<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
removeAccountFromCacheLocked(accounts, accountToRename);
/*
* Update the cached data associated with the renamed
* account.
*/
accounts.userDataCache.put(renamedAccount, tmpData);
accounts.authTokenCache.put(renamedAccount, tmpTokens);
accounts.previousNameCache.put(
renamedAccount,
new AtomicReference<String>(accountToRename.name));
resultAccount = renamedAccount;
if (accounts.userId == UserHandle.USER_OWNER) {
/*
* Owner's account was renamed, rename the account for
* those users with which the account was shared.
*/
List<UserInfo> users = mUserManager.getUsers(true);
for (UserInfo user : users) {
if (!user.isPrimary() && user.isRestricted()) {
renameSharedAccountAsUser(accountToRename, newName, user.id);
}
}
}
sendAccountsChangedBroadcast(accounts.userId);
}
}
}
return resultAccount;
}
@Override
public void removeAccount(IAccountManagerResponse response, Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2060,6 +2240,26 @@ public class AccountManagerService
return true;
}
@Override
public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_NAME, newName);
values.put(ACCOUNTS_PREVIOUS_NAME, account.name);
int r = db.update(
TABLE_SHARED_ACCOUNTS,
values,
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
new String[] { account.name, account.type });
if (r > 0) {
// Recursively rename the account.
renameAccountInternal(accounts, account, newName);
}
return r > 0;
}
@Override
public boolean removeSharedAccountAsUser(Account account, int userId) {
userId = handleIncomingUser(userId);
@@ -2557,6 +2757,7 @@ public class AccountManagerService
+ ACCOUNTS_NAME + " TEXT NOT NULL, "
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PASSWORD + " TEXT, "
+ ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
@@ -2592,6 +2793,10 @@ public class AccountManagerService
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
}
private void addOldAccountNameColumn(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
}
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
db.execSQL(""
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
@@ -2642,6 +2847,11 @@ public class AccountManagerService
oldVersion++;
}
if (oldVersion == 5) {
addOldAccountNameColumn(db);
oldVersion++;
}
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
@@ -3050,6 +3260,7 @@ public class AccountManagerService
}
accounts.userDataCache.remove(account);
accounts.authTokenCache.remove(account);
accounts.previousNameCache.remove(account);
}
/**