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

Commit c37ee227 authored by Carlos Valdivia's avatar Carlos Valdivia
Browse files

Tweak GET_ACCOUNTS behavior and improve memory.

Related to recent permissions and system health changes. This change
will make it so that calls to AccountManager#getAccountsByType will work
for the owning account authenticator even if they don't have
permissions. This is pretty fundamental to having a working
authenticator and it doesn't make sense to have it be disabled (or have
authenticators hack around the framework).

Also changed how TokenCache works so that memory usage is still
predictable (no more than 64kb) but token caching won't be at the mercy
of garbage collection. This is important for writing stable cts tests.

Change-Id: Ib31b550616b266ee5a04eb26b04ba0023ca0cb83
parent 2438c9b2
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -489,7 +489,8 @@ public class AccountManager {
     * <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#GET_ACCOUNTS}.
     * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
     * authenticator that owns the account type.
     *
     * @param type The type of accounts to return, null to retrieve all accounts
     * @return An array of {@link Account}, one per matching account.  Empty
@@ -615,7 +616,8 @@ public class AccountManager {
     * {@link AccountManagerFuture} must not be used on the main thread.
     *
     * <p>This method requires the caller to hold the permission
     * {@link android.Manifest.permission#GET_ACCOUNTS}.
     * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
     * authenticator that owns the account type.
     *
     * @param type The type of accounts to return, must not be null
     * @param features An array of the account features to require,
+158 −121

File changed.

Preview size limit exceeded, changes collapsed.

+115 −48
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@

package com.android.server.accounts;

import android.accounts.Account;
import android.util.LruCache;
import android.util.Pair;

import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -23,10 +29,12 @@ import java.util.List;
import java.util.Objects;

/**
 * TokenCaches manage tokens associated with an account in memory.
 * TokenCaches manage time limited authentication tokens in memory. 
 */
/* default */ class TokenCache {

    private static final int MAX_CACHE_CHARS = 64000;

    private static class Value {
        public final String token;
        public final long expiryEpochMillis;
@@ -38,11 +46,13 @@ import java.util.Objects;
    }

    private static class Key {
        public final Account account;
        public final String packageName;
        public final String tokenType;
        public final byte[] sigDigest;

        public Key(String tokenType, String packageName, byte[] sigDigest) {
        public Key(Account account, String tokenType, String packageName, byte[] sigDigest) {
            this.account = account;
            this.tokenType = tokenType;
            this.packageName = packageName;
            this.sigDigest = sigDigest;
@@ -52,7 +62,8 @@ import java.util.Objects;
        public boolean equals(Object o) {
            if (o != null && o instanceof Key) {
                Key cacheKey = (Key) o;
                return Objects.equals(packageName, cacheKey.packageName)
                return Objects.equals(account, cacheKey.account)
                        && Objects.equals(packageName, cacheKey.packageName)
                        && Objects.equals(tokenType, cacheKey.tokenType)
                        && Arrays.equals(sigDigest, cacheKey.sigDigest);
            } else {
@@ -62,30 +73,20 @@ import java.util.Objects;

        @Override
        public int hashCode() {
            return packageName.hashCode() ^ tokenType.hashCode() ^ Arrays.hashCode(sigDigest);
            return account.hashCode()
                    ^ packageName.hashCode()
                    ^ tokenType.hashCode()
                    ^ Arrays.hashCode(sigDigest);
        }
    }

    /**
     * Map associating basic token lookup information with with actual tokens (and optionally their
     * expiration times). 
     */
    private HashMap<Key, Value> mCachedTokens = new HashMap<>();

    /**
     * Map associated tokens with an Evictor that will manage evicting the token from the cache.
     * This reverse lookup is needed because very little information is given at token invalidation
     * time.
     */
    private HashMap<String, Evictor> mTokenEvictors = new HashMap<>();
    private static class TokenLruCache extends LruCache<Key, Value> {

        private class Evictor {
        private final String mToken;
            private final List<Key> mKeys;

        public Evictor(String token) {
            public Evictor() {
                mKeys = new ArrayList<>();
            mToken = token;
            }

            public void add(Key k) {
@@ -94,13 +95,86 @@ import java.util.Objects;

            public void evict() {
                for (Key k : mKeys) {
                mCachedTokens.remove(k);
                    TokenLruCache.this.remove(k);
                }
            // Clear out the evictor reference.
            mTokenEvictors.remove(mToken);
            }
        }

        /**
         * Map associated tokens with an Evictor that will manage evicting the token from the
         * cache. This reverse lookup is needed because very little information is given at token
         * invalidation time.
         */
        private HashMap<Pair<String, String>, Evictor> mTokenEvictors = new HashMap<>();
        private HashMap<Account, Evictor> mAccountEvictors = new HashMap<>();

        public TokenLruCache() {
            super(MAX_CACHE_CHARS);
        }

        @Override
        protected int sizeOf(Key k, Value v) {
            return v.token.length();
        }

        @Override
        protected void entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal) {
            // When a token has been removed, clean up the associated Evictor.
            if (oldVal != null && newVal == null) {
                /*
                 * This is recursive, but it won't spiral out of control because LruCache is
                 * thread safe and the Evictor can only be removed once.
                 */
                Evictor evictor = mTokenEvictors.remove(oldVal.token);
                if (evictor != null) {
                    evictor.evict();
                }
            }
        }

        public void putToken(Key k, Value v) {
            // Prepare for removal by token string.
            Evictor tokenEvictor = mTokenEvictors.get(v.token);
            if (tokenEvictor == null) {
                tokenEvictor = new Evictor();
            }
            tokenEvictor.add(k);
            mTokenEvictors.put(new Pair<>(k.account.type, v.token), tokenEvictor);

            // Prepare for removal by associated account.
            Evictor accountEvictor = mAccountEvictors.get(k.account);
            if (accountEvictor == null) {
                accountEvictor = new Evictor();
            }
            accountEvictor.add(k);
            mAccountEvictors.put(k.account, tokenEvictor);

            // Only cache the token once we can remove it directly or by account.
            put(k, v);
        }

        public void evict(String accountType, String token) {
            Evictor evictor = mTokenEvictors.get(new Pair<>(accountType, token));
            if (evictor != null) {
                evictor.evict();
            }
            
        }

        public void evict(Account account) {
            Evictor evictor = mAccountEvictors.get(account);
            if (evictor != null) {
                evictor.evict();
            }
        }
    }

    /**
     * Map associating basic token lookup information with with actual tokens (and optionally their
     * expiration times). 
     */
    private TokenLruCache mCachedTokens = new TokenLruCache();

    /**
     * Caches the specified token until the specified expiryMillis. The token will be associated
     * with the given token type, package name, and digest of signatures.
@@ -112,51 +186,44 @@ import java.util.Objects;
     * @param expiryMillis
     */
    public void put(
            Account account,
            String token,
            String tokenType,
            String packageName,
            byte[] sigDigest,
            long expiryMillis) {
        Preconditions.checkNotNull(account);
        if (token == null || System.currentTimeMillis() > expiryMillis) {
            return;
        }
        Key k = new Key(tokenType, packageName, sigDigest);
        // Prep evictor. No token should be cached without a corresponding evictor.
        Evictor evictor = mTokenEvictors.get(token);
        if (evictor == null) {
            evictor = new Evictor(token);
        }
        evictor.add(k);
        mTokenEvictors.put(token, evictor);
        // Then cache values.
        Key k = new Key(account, tokenType, packageName, sigDigest);
        Value v = new Value(token, expiryMillis);
        mCachedTokens.put(k, v);
        mCachedTokens.putToken(k, v);
    }

    /**
     * Evicts the specified token from the cache. This should be called as part of a token
     * invalidation workflow.
     */
    public void remove(String token) {
        Evictor evictor = mTokenEvictors.get(token);
        if (evictor == null) {
            // This condition is expected if the token isn't cached.
            return;
    public void remove(String accountType, String token) {
        mCachedTokens.evict(accountType, token);
    }
        evictor.evict();

    public void remove(Account account) {
        mCachedTokens.evict(account);
    }

    /**
     * Gets a token from the cache if possible.
     */
    public String get(String tokenType, String packageName, byte[] sigDigest) {
        Key k = new Key(tokenType, packageName, sigDigest);
    public String get(Account account, String tokenType, String packageName, byte[] sigDigest) {
        Key k = new Key(account, tokenType, packageName, sigDigest);
        Value v = mCachedTokens.get(k);
        long currentTime = System.currentTimeMillis();
        if (v != null && currentTime < v.expiryEpochMillis) {
            return v.token;
        } else if (v != null) {
            remove(v.token);
            remove(account.type, v.token);
        }
        return null;
    }