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