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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
/*
* 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.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
/**
* Provides methods to load resources data from APKs ({@code .apk}) and resources tables
* (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}.
*/
public class ResourcesProvider implements AutoCloseable, Closeable {
private static final String TAG = "ResourcesProvider";
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mOpen = true;
@GuardedBy("mLock")
private int mOpenCount = 0;
@GuardedBy("mLock")
private final ApkAssets mApkAssets;
/**
* Creates an empty ResourcesProvider with no resource data. This is useful for loading
* file-based assets not associated with resource identifiers.
*
* @param assetsProvider the assets provider that implements the loading of file-based resources
*/
@NonNull
public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* @param fileDescriptor the file descriptor of the APK to load
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
throws IOException {
return loadFromApk(fileDescriptor, null /* assetsProvider */);
}
/**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The assets provider can override the loading of files within the APK and can provide
* entirely new files that do not exist in the APK.
*
* @param fileDescriptor the file descriptor of the APK to load
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The assets provider can override the loading of files within the APK and can provide
* entirely new files that do not exist in the APK.
*
* @param fileDescriptor the file descriptor of the APK to load
* @param offset The location within the file that the apk starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
* @hide
*/
@VisibleForTesting
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
long offset, long length, @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
* Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The resources table format is not an archive format and therefore cannot asset files
* within itself. The assets provider can instead provide files that are potentially referenced
* by path in the resources table.
*
* @param fileDescriptor the file descriptor of the resources table to load
* @param assetsProvider the assets provider that implements the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
* Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
*
* The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The resources table format is not an archive format and therefore cannot asset files
* within itself. The assets provider can instead provide files that are potentially referenced
* by path in the resources table.
*
* @param fileDescriptor the file descriptor of the resources table to load
* @param offset The location within the file that the table starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
* @hide
*/
@VisibleForTesting
@NonNull
public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
long offset, long length, @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
* Read from a split installed alongside the application, which may not have been
* loaded initially because the application requested isolated split loading.
*
* @param context a context of the package that contains the split
* @param splitName the name of the split to load
*/
@NonNull
public static ResourcesProvider loadFromSplit(@NonNull Context context,
@NonNull String splitName) throws IOException {
ApplicationInfo appInfo = context.getApplicationInfo();
int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
if (splitIndex < 0) {
throw new IllegalArgumentException("Split " + splitName + " not found");
}
String splitPath = appInfo.getSplitCodePaths()[splitIndex];
return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER,
null /* assetsProvider */));
}
/**
* Creates a ResourcesProvider from a directory path.
*
* File-based resources will be resolved within the directory as if the directory is an APK.
*
* @param path the path of the directory to treat as an APK
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*/
@NonNull
public static ResourcesProvider loadFromDirectory(@NonNull String path,
@Nullable AssetsProvider assetsProvider) throws IOException {
return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
private ResourcesProvider(@NonNull ApkAssets apkAssets) {
this.mApkAssets = apkAssets;
}
/** @hide */
@NonNull
public ApkAssets getApkAssets() {
return mApkAssets;
}
final void incrementRefCount() {
synchronized (mLock) {
if (!mOpen) {
throw new IllegalStateException("Operation failed: resources provider is closed");
}
mOpenCount++;
}
}
final void decrementRefCount() {
synchronized (mLock) {
mOpenCount--;
}
}
/**
* Frees internal data structures. Closed providers can no longer be added to
* {@link ResourcesLoader ResourcesLoader(s)}.
*
* @throws IllegalStateException if provider is currently used by a ResourcesLoader
*/
@Override
public void close() {
synchronized (mLock) {
if (!mOpen) {
return;
}
if (mOpenCount != 0) {
throw new IllegalStateException("Failed to close provider used by " + mOpenCount
+ " ResourcesLoader instances");
}
mOpen = false;
}
try {
mApkAssets.close();
} catch (Throwable ignored) {
}
}
@Override
protected void finalize() throws Throwable {
synchronized (mLock) {
if (mOpenCount != 0) {
Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
+ mOpenCount);
}
}
}
}
|