summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/res/ThemedResourceCache.java
blob: 9a2d061474e6cea3597cf62b5b73a06e8c7637be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.res;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources.Theme;
import android.content.res.Resources.ThemeKey;
import android.util.LongSparseArray;
import android.util.ArrayMap;

import java.lang.ref.WeakReference;

/**
 * Data structure used for caching data against themes.
 *
 * @param <T> type of data to cache
 */
abstract class ThemedResourceCache<T> {
    private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
    private LongSparseArray<WeakReference<T>> mUnthemedEntries;
    private LongSparseArray<WeakReference<T>> mNullThemedEntries;

    /**
     * Adds a new theme-dependent entry to the cache.
     *
     * @param key a key that uniquely identifies the entry
     * @param theme the theme against which this entry was inflated, or
     *              {@code null} if the entry has no theme applied
     * @param entry the entry to cache
     */
    public void put(long key, @Nullable Theme theme, @NonNull T entry) {
        put(key, theme, entry, true);
    }

    /**
     * Adds a new entry to the cache.
     *
     * @param key a key that uniquely identifies the entry
     * @param theme the theme against which this entry was inflated, or
     *              {@code null} if the entry has no theme applied
     * @param entry the entry to cache
     * @param usesTheme {@code true} if the entry is affected theme changes,
     *                  {@code false} otherwise
     */
    public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
        if (entry == null) {
            return;
        }

        synchronized (this) {
            final LongSparseArray<WeakReference<T>> entries;
            if (!usesTheme) {
                entries = getUnthemedLocked(true);
            } else {
                entries = getThemedLocked(theme, true);
            }
            if (entries != null) {
                entries.put(key, new WeakReference<>(entry));
            }
        }
    }

    /**
     * Returns an entry from the cache.
     *
     * @param key a key that uniquely identifies the entry
     * @param theme the theme where the entry will be used
     * @return a cached entry, or {@code null} if not in the cache
     */
    @Nullable
    public T get(long key, @Nullable Theme theme) {
        // The themed (includes null-themed) and unthemed caches are mutually
        // exclusive, so we'll give priority to whichever one we think we'll
        // hit first. Since most of the framework drawables are themed, that's
        // probably going to be the themed cache.
        synchronized (this) {
            final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false);
            if (themedEntries != null) {
                final WeakReference<T> themedEntry = themedEntries.get(key);
                if (themedEntry != null) {
                    return themedEntry.get();
                }
            }

            final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false);
            if (unthemedEntries != null) {
                final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
                if (unthemedEntry != null) {
                    return unthemedEntry.get();
                }
            }
        }

        return null;
    }

    /**
     * Prunes cache entries that have been invalidated by a configuration
     * change.
     *
     * @param configChanges a bitmask of configuration changes
     */
    public void onConfigurationChange(int configChanges) {
        prune(configChanges);
    }

    /**
     * Returns whether a cached entry has been invalidated by a configuration
     * change.
     *
     * @param entry a cached entry
     * @param configChanges a non-zero bitmask of configuration changes
     * @return {@code true} if the entry is invalid, {@code false} otherwise
     */
    protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges);

    /**
     * Returns the cached data for the specified theme, optionally creating a
     * new entry if one does not already exist.
     *
     * @param t the theme for which to return cached data
     * @param create {@code true} to create an entry if one does not already
     *               exist, {@code false} otherwise
     * @return the cached data for the theme, or {@code null} if the cache is
     *         empty and {@code create} was {@code false}
     */
    @Nullable
    private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) {
        if (t == null) {
            if (mNullThemedEntries == null && create) {
                mNullThemedEntries = new LongSparseArray<>(1);
            }
            return mNullThemedEntries;
        }

        if (mThemedEntries == null) {
            if (create) {
                mThemedEntries = new ArrayMap<>(1);
            } else {
                return null;
            }
        }

        final ThemeKey key = t.getKey();
        LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key);
        if (cache == null && create) {
            cache = new LongSparseArray<>(1);

            final ThemeKey keyClone = key.clone();
            mThemedEntries.put(keyClone, cache);
        }

        return cache;
    }

    /**
     * Returns the theme-agnostic cached data.
     *
     * @param create {@code true} to create an entry if one does not already
     *               exist, {@code false} otherwise
     * @return the theme-agnostic cached data, or {@code null} if the cache is
     *         empty and {@code create} was {@code false}
     */
    @Nullable
    private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) {
        if (mUnthemedEntries == null && create) {
            mUnthemedEntries = new LongSparseArray<>(1);
        }
        return mUnthemedEntries;
    }

    /**
     * Prunes cache entries affected by configuration changes or where weak
     * references have expired.
     *
     * @param configChanges a bitmask of configuration changes, or {@code 0} to
     *                      simply prune missing weak references
     * @return {@code true} if the cache is completely empty after pruning
     */
    private boolean prune(int configChanges) {
        synchronized (this) {
            if (mThemedEntries != null) {
                for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
                    if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
                        mThemedEntries.removeAt(i);
                    }
                }
            }

            pruneEntriesLocked(mNullThemedEntries, configChanges);
            pruneEntriesLocked(mUnthemedEntries, configChanges);

            return mThemedEntries == null && mNullThemedEntries == null
                    && mUnthemedEntries == null;
        }
    }

    private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
            int configChanges) {
        if (entries == null) {
            return true;
        }

        for (int i = entries.size() - 1; i >= 0; i--) {
            final WeakReference<T> ref = entries.valueAt(i);
            if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
                entries.removeAt(i);
            }
        }

        return entries.size() == 0;
    }

    private boolean pruneEntryLocked(@Nullable T entry, int configChanges) {
        return entry == null || (configChanges != 0
                && shouldInvalidateEntry(entry, configChanges));
    }
}