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