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