Merge "Allow authenticators to rename accounts." into lmp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
75111d83e6
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user