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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
/*
* Copyright (C) 2019 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.loader;
import android.annotation.NonNull;
import android.content.res.ApkAssets;
import android.content.res.Resources;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A container for supplying {@link ResourcesProvider ResourcesProvider(s)} to {@link Resources}
* objects.
*
* <p>{@link ResourcesLoader ResourcesLoader(s)} are added to Resources objects to supply
* additional resources and assets or modify the values of existing resources and assets. Multiple
* Resources objects can share the same ResourcesLoaders and ResourcesProviders. Changes to the list
* of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
* objects that use the loader.
*
* <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
* override the resources and assets of loaders added before itself.
*
* <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
* provider will override the resources and assets of providers listed before itself.
*
* <p>Modifying the list of providers a loader contains or the list of loaders a Resources object
* contains can cause lock contention with the UI thread. APIs that modify the lists of loaders or
* providers should only be used on the UI thread. Providers can be instantiated on any thread
* without causing lock contention.
*/
public class ResourcesLoader {
private final Object mLock = new Object();
@GuardedBy("mLock")
private ApkAssets[] mApkAssets;
@GuardedBy("mLock")
private ResourcesProvider[] mPreviousProviders;
@GuardedBy("mLock")
private ResourcesProvider[] mProviders;
@GuardedBy("mLock")
private ArrayMap<WeakReference<Object>, UpdateCallbacks> mChangeCallbacks = new ArrayMap<>();
/** @hide */
public interface UpdateCallbacks {
/**
* Invoked when a {@link ResourcesLoader} has a {@link ResourcesProvider} added, removed,
* or reordered.
*
* @param loader the loader that was updated
*/
void onLoaderUpdated(@NonNull ResourcesLoader loader);
}
/**
* Retrieves the list of providers loaded into this instance. Providers are listed in increasing
* precedence order. A provider will override the values of providers listed before itself.
*/
@NonNull
public List<ResourcesProvider> getProviders() {
synchronized (mLock) {
return mProviders == null ? Collections.emptyList() : Arrays.asList(mProviders);
}
}
/**
* Appends a provider to the end of the provider list. If the provider is already present in the
* loader list, the list will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProvider the provider to add
*/
public void addProvider(@NonNull ResourcesProvider resourcesProvider) {
synchronized (mLock) {
mProviders = ArrayUtils.appendElement(ResourcesProvider.class, mProviders,
resourcesProvider);
notifyProvidersChangedLocked();
}
}
/**
* Removes a provider from the provider list. If the provider is not present in the provider
* list, the list will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProvider the provider to remove
*/
public void removeProvider(@NonNull ResourcesProvider resourcesProvider) {
synchronized (mLock) {
mProviders = ArrayUtils.removeElement(ResourcesProvider.class, mProviders,
resourcesProvider);
notifyProvidersChangedLocked();
}
}
/**
* Sets the list of providers.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProviders the new providers
*/
public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) {
synchronized (mLock) {
mProviders = resourcesProviders.toArray(new ResourcesProvider[0]);
notifyProvidersChangedLocked();
}
}
/**
* Removes all {@link ResourcesProvider ResourcesProvider(s)}.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*/
public void clearProviders() {
synchronized (mLock) {
mProviders = null;
notifyProvidersChangedLocked();
}
}
/**
* Retrieves the list of {@link ApkAssets} used by the providers.
*
* @hide
*/
@NonNull
public List<ApkAssets> getApkAssets() {
synchronized (mLock) {
if (mApkAssets == null) {
return Collections.emptyList();
}
return Arrays.asList(mApkAssets);
}
}
/**
* Registers a callback to be invoked when {@link ResourcesProvider ResourcesProvider(s)}
* change.
* @param instance the instance tied to the callback
* @param callbacks the callback to invoke
*
* @hide
*/
public void registerOnProvidersChangedCallback(@NonNull Object instance,
@NonNull UpdateCallbacks callbacks) {
synchronized (mLock) {
mChangeCallbacks.put(new WeakReference<>(instance), callbacks);
}
}
/**
* Removes a previously registered callback.
* @param instance the instance tied to the callback
*
* @hide
*/
public void unregisterOnProvidersChangedCallback(@NonNull Object instance) {
synchronized (mLock) {
for (int i = 0, n = mChangeCallbacks.size(); i < n; i++) {
final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
if (instance == key.get()) {
mChangeCallbacks.removeAt(i);
return;
}
}
}
}
/** Returns whether the arrays contain the same provider instances in the same order. */
private static boolean arrayEquals(ResourcesProvider[] a1, ResourcesProvider[] a2) {
if (a1 == a2) {
return true;
}
if (a1 == null || a2 == null) {
return false;
}
if (a1.length != a2.length) {
return false;
}
// Check that the arrays contain the exact same instances in the same order. Providers do
// not have any form of equivalence checking of whether the contents of two providers have
// equivalent apk assets.
for (int i = 0, n = a1.length; i < n; i++) {
if (a1[i] != a2[i]) {
return false;
}
}
return true;
}
/**
* Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader
* uses changes.
*/
private void notifyProvidersChangedLocked() {
final ArraySet<UpdateCallbacks> uniqueCallbacks = new ArraySet<>();
if (arrayEquals(mPreviousProviders, mProviders)) {
return;
}
if (mProviders == null || mProviders.length == 0) {
mApkAssets = null;
} else {
mApkAssets = new ApkAssets[mProviders.length];
for (int i = 0, n = mProviders.length; i < n; i++) {
mProviders[i].incrementRefCount();
mApkAssets[i] = mProviders[i].getApkAssets();
}
}
// Decrement the ref count after incrementing the new provider ref count so providers
// present before and after this method do not drop to zero references.
if (mPreviousProviders != null) {
for (ResourcesProvider provider : mPreviousProviders) {
provider.decrementRefCount();
}
}
mPreviousProviders = mProviders;
for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
if (key.refersTo(null)) {
mChangeCallbacks.removeAt(i);
} else {
uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
}
}
for (int i = 0, n = uniqueCallbacks.size(); i < n; i++) {
uniqueCallbacks.valueAt(i).onLoaderUpdated(this);
}
}
}
|