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      */
put(long key, @Nullable Theme theme, @NonNull T entry)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      */
put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme)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
get(long key, @Nullable Theme theme)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      */
onConfigurationChange(@onfig int configChanges)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      */
shouldInvalidateEntry(@onNull T entry, int configChanges)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
getThemedLocked(@ullable Theme t, boolean create)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
getUnthemedLocked(boolean create)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      */
prune(@onfig int configChanges)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 
pruneEntriesLocked(@ullable LongSparseArray<WeakReference<T>> entries, @Config int configChanges)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 
pruneEntryLocked(@ullable T entry, @Config int configChanges)230     private boolean pruneEntryLocked(@Nullable T entry, @Config int configChanges) {
231         return entry == null || (configChanges != 0
232                 && shouldInvalidateEntry(entry, configChanges));
233     }
234 }
235