• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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