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.compat.annotation.UnsupportedAppUsage; 22 import android.content.pm.ActivityInfo.Config; 23 import android.content.res.Resources.Theme; 24 import android.content.res.Resources.ThemeKey; 25 import android.util.ArrayMap; 26 import android.util.LongSparseArray; 27 28 import java.lang.ref.WeakReference; 29 30 /** 31 * Data structure used for caching data against themes. 32 * 33 * @param <T> type of data to cache 34 */ 35 abstract class ThemedResourceCache<T> { 36 public static final int UNDEFINED_GENERATION = -1; 37 @UnsupportedAppUsage 38 private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries; 39 private LongSparseArray<WeakReference<T>> mUnthemedEntries; 40 private LongSparseArray<WeakReference<T>> mNullThemedEntries; 41 42 private int mGeneration; 43 44 /** 45 * Adds a new theme-dependent entry to the cache. 46 * 47 * @param key a key that uniquely identifies the entry 48 * @param theme the theme against which this entry was inflated, or 49 * {@code null} if the entry has no theme applied 50 * @param entry the entry to cache 51 * @param generation The generation of the cache to compare against before storing 52 */ put(long key, @Nullable Theme theme, @NonNull T entry, int generation)53 public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation) { 54 put(key, theme, entry, generation, true); 55 } 56 57 /** 58 * Adds a new entry to the cache. 59 * 60 * @param key a key that uniquely identifies the entry 61 * @param theme the theme against which this entry was inflated, or 62 * {@code null} if the entry has no theme applied 63 * @param entry the entry to cache 64 * @param generation The generation of the cache to compare against before storing 65 * @param usesTheme {@code true} if the entry is affected theme changes, 66 * {@code false} otherwise 67 */ put(long key, @Nullable Theme theme, @NonNull T entry, int generation, boolean usesTheme)68 public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation, 69 boolean usesTheme) { 70 if (entry == null) { 71 return; 72 } 73 74 synchronized (this) { 75 final LongSparseArray<WeakReference<T>> entries; 76 if (!usesTheme) { 77 entries = getUnthemedLocked(true); 78 } else { 79 entries = getThemedLocked(theme, true); 80 } 81 if (entries != null 82 && ((generation == mGeneration) || (generation == UNDEFINED_GENERATION))) { 83 entries.put(key, new WeakReference<>(entry)); 84 } 85 } 86 } 87 88 /** 89 * Returns the current generation of the cache 90 * 91 * @return The current generation 92 */ getGeneration()93 public int getGeneration() { 94 return mGeneration; 95 } 96 97 /** 98 * Returns an entry from the cache. 99 * 100 * @param key a key that uniquely identifies the entry 101 * @param theme the theme where the entry will be used 102 * @return a cached entry, or {@code null} if not in the cache 103 */ 104 @Nullable get(long key, @Nullable Theme theme)105 public T get(long key, @Nullable Theme theme) { 106 // The themed (includes null-themed) and unthemed caches are mutually 107 // exclusive, so we'll give priority to whichever one we think we'll 108 // hit first. Since most of the framework drawables are themed, that's 109 // probably going to be the themed cache. 110 synchronized (this) { 111 final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false); 112 if (themedEntries != null) { 113 final WeakReference<T> themedEntry = themedEntries.get(key); 114 if (themedEntry != null) { 115 return themedEntry.get(); 116 } 117 } 118 119 final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false); 120 if (unthemedEntries != null) { 121 final WeakReference<T> unthemedEntry = unthemedEntries.get(key); 122 if (unthemedEntry != null) { 123 return unthemedEntry.get(); 124 } 125 } 126 } 127 128 return null; 129 } 130 131 132 /** 133 * Prunes cache entries that have been invalidated by a configuration 134 * change. 135 * 136 * @param configChanges a bitmask of configuration changes 137 */ 138 @UnsupportedAppUsage onConfigurationChange(@onfig int configChanges)139 public void onConfigurationChange(@Config int configChanges) { 140 synchronized (this) { 141 pruneLocked(configChanges); 142 mGeneration++; 143 } 144 } 145 146 /** 147 * Returns whether a cached entry has been invalidated by a configuration 148 * change. 149 * 150 * @param entry a cached entry 151 * @param configChanges a non-zero bitmask of configuration changes 152 * @return {@code true} if the entry is invalid, {@code false} otherwise 153 */ shouldInvalidateEntry(@onNull T entry, int configChanges)154 protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges); 155 156 /** 157 * Returns the cached data for the specified theme, optionally creating a 158 * new entry if one does not already exist. 159 * 160 * @param t the theme for which to return cached data 161 * @param create {@code true} to create an entry if one does not already 162 * exist, {@code false} otherwise 163 * @return the cached data for the theme, or {@code null} if the cache is 164 * empty and {@code create} was {@code false} 165 */ 166 @Nullable getThemedLocked(@ullable Theme t, boolean create)167 private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) { 168 if (t == null) { 169 if (mNullThemedEntries == null && create) { 170 mNullThemedEntries = new LongSparseArray<>(1); 171 } 172 return mNullThemedEntries; 173 } 174 175 if (mThemedEntries == null) { 176 if (create) { 177 mThemedEntries = new ArrayMap<>(1); 178 } else { 179 return null; 180 } 181 } 182 183 final ThemeKey key = t.getKey(); 184 LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key); 185 if (cache == null && create) { 186 cache = new LongSparseArray<>(1); 187 188 final ThemeKey keyClone = key.clone(); 189 mThemedEntries.put(keyClone, cache); 190 } 191 192 return cache; 193 } 194 195 /** 196 * Returns the theme-agnostic cached data. 197 * 198 * @param create {@code true} to create an entry if one does not already 199 * exist, {@code false} otherwise 200 * @return the theme-agnostic cached data, or {@code null} if the cache is 201 * empty and {@code create} was {@code false} 202 */ 203 @Nullable getUnthemedLocked(boolean create)204 private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) { 205 if (mUnthemedEntries == null && create) { 206 mUnthemedEntries = new LongSparseArray<>(1); 207 } 208 return mUnthemedEntries; 209 } 210 211 /** 212 * Prunes cache entries affected by configuration changes or where weak 213 * references have expired. 214 * 215 * @param configChanges a bitmask of configuration changes, or {@code 0} to 216 * simply prune missing weak references 217 * @return {@code true} if the cache is completely empty after pruning 218 */ pruneLocked(@onfig int configChanges)219 private boolean pruneLocked(@Config int configChanges) { 220 if (mThemedEntries != null) { 221 for (int i = mThemedEntries.size() - 1; i >= 0; i--) { 222 if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { 223 mThemedEntries.removeAt(i); 224 } 225 } 226 } 227 228 pruneEntriesLocked(mNullThemedEntries, configChanges); 229 pruneEntriesLocked(mUnthemedEntries, configChanges); 230 231 return mThemedEntries == null && mNullThemedEntries == null 232 && mUnthemedEntries == null; 233 } 234 pruneEntriesLocked(@ullable LongSparseArray<WeakReference<T>> entries, @Config int configChanges)235 private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, 236 @Config int configChanges) { 237 if (entries == null) { 238 return true; 239 } 240 241 for (int i = entries.size() - 1; i >= 0; i--) { 242 final WeakReference<T> ref = entries.valueAt(i); 243 if (ref == null || pruneEntryLocked(ref.get(), configChanges)) { 244 entries.removeAt(i); 245 } 246 } 247 248 return entries.size() == 0; 249 } 250 pruneEntryLocked(@ullable T entry, @Config int configChanges)251 private boolean pruneEntryLocked(@Nullable T entry, @Config int configChanges) { 252 return entry == null || (configChanges != 0 253 && shouldInvalidateEntry(entry, configChanges)); 254 } 255 clear()256 public synchronized void clear() { 257 if (mThemedEntries != null) { 258 mThemedEntries.clear(); 259 } 260 261 if (mUnthemedEntries != null) { 262 mUnthemedEntries.clear(); 263 } 264 265 if (mNullThemedEntries != null) { 266 mNullThemedEntries.clear(); 267 } 268 } 269 } 270