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

Commit 88817de8 authored by Carlos Valdivia's avatar Carlos Valdivia Committed by Android (Google) Code Review
Browse files

Merge "Tweak GET_ACCOUNTS behavior and improve memory." into mnc-dev

parents dd14bad8 c37ee227
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;
    }