Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 34f62afa authored by Carlos Valdivia's avatar Carlos Valdivia Committed by Android Git Automerger
Browse files

am 26680d5c: Merge "Allow authenticators to rename accounts." into lmp-dev

* commit '26680d5c344a773756975e36b250cd2656e08f60':
  Allow authenticators to rename accounts.
parents 3a35c792 75111d83
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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);
+66 −0
Original line number Diff line number Diff line
@@ -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.
+6 −0
Original line number Diff line number Diff line
@@ -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);

}
+212 −1
Original line number Diff line number Diff line
@@ -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);
    }

    /**