Home | History | Annotate | Download | only in res
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content.res;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.pm.ActivityInfo.Config;
     22 import android.content.res.Resources.Theme;
     23 import android.content.res.Resources.ThemeKey;
     24 import android.util.LongSparseArray;
     25 import android.util.ArrayMap;
     26 
     27 import java.lang.ref.WeakReference;
     28 
     29 /**
     30  * Data structure used for caching data against themes.
     31  *
     32  * @param <T> type of data to cache
     33  */
     34 abstract class ThemedResourceCache<T> {
     35     private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
     36     private LongSparseArray<WeakReference<T>> mUnthemedEntries;
     37     private LongSparseArray<WeakReference<T>> mNullThemedEntries;
     38 
     39     /**
     40      * Adds a new theme-dependent entry to the cache.
     41      *
     42      * @param key a key that uniquely identifies the entry
     43      * @param theme the theme against which this entry was inflated, or
     44      *              {@code null} if the entry has no theme applied
     45      * @param entry the entry to cache
     46      */
     47     public void put(long key, @Nullable Theme theme, @NonNull T entry) {
     48         put(key, theme, entry, true);
     49     }
     50 
     51     /**
     52      * Adds a new entry to the cache.
     53      *
     54      * @param key a key that uniquely identifies the entry
     55      * @param theme the theme against which this entry was inflated, or
     56      *              {@code null} if the entry has no theme applied
     57      * @param entry the entry to cache
     58      * @param usesTheme {@code true} if the entry is affected theme changes,
     59      *                  {@code false} otherwise
     60      */
     61     public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
     62         if (entry == null) {
     63             return;
     64         }
     65 
     66         synchronized (this) {
     67             final LongSparseArray<WeakReference<T>> entries;
     68             if (!usesTheme) {
     69                 entries = getUnthemedLocked(true);
     70             } else {
     71                 entries = getThemedLocked(theme, true);
     72             }
     73             if (entries != null) {
     74                 entries.put(key, new WeakReference<>(entry));
     75             }
     76         }
     77     }
     78 
     79     /**
     80      * Returns an entry from the cache.
     81      *
     82      * @param key a key that uniquely identifies the entry
     83      * @param theme the theme where the entry will be used
     84      * @return a cached entry, or {@code null} if not in the cache
     85      */
     86     @Nullable
     87     public T get(long key, @Nullable Theme theme) {
     88         // The themed (includes null-themed) and unthemed caches are mutually
     89         // exclusive, so we'll give priority to whichever one we think we'll
     90         // hit first. Since most of the framework drawables are themed, that's
     91         // probably going to be the themed cache.
     92         synchronized (this) {
     93             final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false);
     94             if (themedEntries != null) {
     95                 final WeakReference<T> themedEntry = themedEntries.get(key);
     96                 if (themedEntry != null) {
     97                     return themedEntry.get();
     98                 }
     99             }
    100 
    101             final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false);
    102             if (unthemedEntries != null) {
    103                 final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
    104                 if (unthemedEntry != null) {
    105                     return unthemedEntry.get();
    106                 }
    107             }
    108         }
    109 
    110         return null;
    111     }
    112 
    113     /**
    114      * Prunes cache entries that have been invalidated by a configuration
    115      * change.
    116      *
    117      * @param configChanges a bitmask of configuration changes
    118      */
    119     public void onConfigurationChange(@Config int configChanges) {
    120         prune(configChanges);
    121     }
    122 
    123     /**
    124      * Returns whether a cached entry has been invalidated by a configuration
    125      * change.
    126      *
    127      * @param entry a cached entry
    128      * @param configChanges a non-zero bitmask of configuration changes
    129      * @return {@code true} if the entry is invalid, {@code false} otherwise
    130      */
    131     protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges);
    132 
    133     /**
    134      * Returns the cached data for the specified theme, optionally creating a
    135      * new entry if one does not already exist.
    136      *
    137      * @param t the theme for which to return cached data
    138      * @param create {@code true} to create an entry if one does not already
    139      *               exist, {@code false} otherwise
    140      * @return the cached data for the theme, or {@code null} if the cache is
    141      *         empty and {@code create} was {@code false}
    142      */
    143     @Nullable
    144     private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) {
    145         if (t == null) {
    146             if (mNullThemedEntries == null && create) {
    147                 mNullThemedEntries = new LongSparseArray<>(1);
    148             }
    149             return mNullThemedEntries;
    150         }
    151 
    152         if (mThemedEntries == null) {
    153             if (create) {
    154                 mThemedEntries = new ArrayMap<>(1);
    155             } else {
    156                 return null;
    157             }
    158         }
    159 
    160         final ThemeKey key = t.getKey();
    161         LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key);
    162         if (cache == null && create) {
    163             cache = new LongSparseArray<>(1);
    164 
    165             final ThemeKey keyClone = key.clone();
    166             mThemedEntries.put(keyClone, cache);
    167         }
    168 
    169         return cache;
    170     }
    171 
    172     /**
    173      * Returns the theme-agnostic cached data.
    174      *
    175      * @param create {@code true} to create an entry if one does not already
    176      *               exist, {@code false} otherwise
    177      * @return the theme-agnostic cached data, or {@code null} if the cache is
    178      *         empty and {@code create} was {@code false}
    179      */
    180     @Nullable
    181     private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) {
    182         if (mUnthemedEntries == null && create) {
    183             mUnthemedEntries = new LongSparseArray<>(1);
    184         }
    185         return mUnthemedEntries;
    186     }
    187 
    188     /**
    189      * Prunes cache entries affected by configuration changes or where weak
    190      * references have expired.
    191      *
    192      * @param configChanges a bitmask of configuration changes, or {@code 0} to
    193      *                      simply prune missing weak references
    194      * @return {@code true} if the cache is completely empty after pruning
    195      */
    196     private boolean prune(@Config int configChanges) {
    197         synchronized (this) {
    198             if (mThemedEntries != null) {
    199                 for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
    200                     if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
    201                         mThemedEntries.removeAt(i);
    202                     }
    203                 }
    204             }
    205 
    206             pruneEntriesLocked(mNullThemedEntries, configChanges);
    207             pruneEntriesLocked(mUnthemedEntries, configChanges);
    208 
    209             return mThemedEntries == null && mNullThemedEntries == null
    210                     && mUnthemedEntries == null;
    211         }
    212     }
    213 
    214     private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
    215             @Config int configChanges) {
    216         if (entries == null) {
    217             return true;
    218         }
    219 
    220         for (int i = entries.size() - 1; i >= 0; i--) {
    221             final WeakReference<T> ref = entries.valueAt(i);
    222             if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
    223                 entries.removeAt(i);
    224             }
    225         }
    226 
    227         return entries.size() == 0;
    228     }
    229 
    230     private boolean pruneEntryLocked(@Nullable T entry, @Config int configChanges) {
    231         return entry == null || (configChanges != 0
    232                 && shouldInvalidateEntry(entry, configChanges));
    233     }
    234 }
    235