blob: 143c00dd4d81e7c3c8e520b22af1c121d98450fd [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
2 * Copyright (C) 2017 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 */
16package android.content.res;
17
18import android.annotation.IntDef;
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.compat.annotation.UnsupportedAppUsage;
22import android.content.om.OverlayableInfo;
23import android.content.res.loader.AssetsProvider;
24import android.content.res.loader.ResourcesProvider;
25import android.text.TextUtils;
26
27import com.android.internal.annotations.GuardedBy;
28
29import dalvik.annotation.optimization.CriticalNative;
30
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
36import java.util.Objects;
37
38/**
39 * The loaded, immutable, in-memory representation of an APK.
40 *
41 * The main implementation is native C++ and there is very little API surface exposed here. The APK
42 * is mainly accessed via {@link AssetManager}.
43 *
44 * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
45 * making the creation of AssetManagers very cheap.
46 * @hide
47 */
48public final class ApkAssets {
49
50 /**
51 * The apk assets contains framework resource values specified by the system.
52 * This allows some functions to filter out this package when computing what
53 * configurations/resources are available.
54 */
55 public static final int PROPERTY_SYSTEM = 1 << 0;
56
57 /**
58 * The apk assets is a shared library or was loaded as a shared library by force.
59 * The package ids of dynamic apk assets are assigned at runtime instead of compile time.
60 */
61 public static final int PROPERTY_DYNAMIC = 1 << 1;
62
63 /**
64 * The apk assets has been loaded dynamically using a {@link ResourcesProvider}.
65 * Loader apk assets overlay resources like RROs except they are not backed by an idmap.
66 */
67 public static final int PROPERTY_LOADER = 1 << 2;
68
69 /**
70 * The apk assets is a RRO.
71 * An RRO overlays resource values of its target package.
72 */
73 private static final int PROPERTY_OVERLAY = 1 << 3;
74
75 /**
76 * The apk assets is owned by the application running in this process and incremental crash
77 * protections for this APK must be disabled.
78 */
79 public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4;
80
81 /** Flags that change the behavior of loaded apk assets. */
82 @IntDef(prefix = { "PROPERTY_" }, value = {
83 PROPERTY_SYSTEM,
84 PROPERTY_DYNAMIC,
85 PROPERTY_LOADER,
86 PROPERTY_OVERLAY,
87 })
88 @Retention(RetentionPolicy.SOURCE)
89 public @interface PropertyFlags {}
90
91 /** The path used to load the apk assets represents an APK file. */
92 private static final int FORMAT_APK = 0;
93
94 /** The path used to load the apk assets represents an idmap file. */
95 private static final int FORMAT_IDMAP = 1;
96
97 /** The path used to load the apk assets represents an resources.arsc file. */
98 private static final int FORMAT_ARSC = 2;
99
100 /** the path used to load the apk assets represents a directory. */
101 private static final int FORMAT_DIR = 3;
102
103 // Format types that change how the apk assets are loaded.
104 @IntDef(prefix = { "FORMAT_" }, value = {
105 FORMAT_APK,
106 FORMAT_IDMAP,
107 FORMAT_ARSC,
108 FORMAT_DIR
109 })
110 @Retention(RetentionPolicy.SOURCE)
111 public @interface FormatType {}
112
113 @GuardedBy("this")
114 private long mNativePtr; // final, except cleared in finalizer.
115
116 @Nullable
117 @GuardedBy("this")
118 private final StringBlock mStringBlock; // null or closed if mNativePtr = 0.
119
120 @PropertyFlags
121 private final int mFlags;
122
123 @Nullable
124 private final AssetsProvider mAssets;
125
126 /**
127 * Creates a new ApkAssets instance from the given path on disk.
128 *
129 * @param path The path to an APK on disk.
130 * @return a new instance of ApkAssets.
131 * @throws IOException if a disk I/O error or parsing error occurred.
132 */
133 public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
134 return loadFromPath(path, 0 /* flags */);
135 }
136
137 /**
138 * Creates a new ApkAssets instance from the given path on disk.
139 *
140 * @param path The path to an APK on disk.
141 * @param flags flags that change the behavior of loaded apk assets
142 * @return a new instance of ApkAssets.
143 * @throws IOException if a disk I/O error or parsing error occurred.
144 */
145 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
146 throws IOException {
147 return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
148 }
149
150 /**
151 * Creates a new ApkAssets instance from the given path on disk.
152 *
153 * @param path The path to an APK on disk.
154 * @param flags flags that change the behavior of loaded apk assets
155 * @param assets The assets provider that overrides the loading of file-based resources
156 * @return a new instance of ApkAssets.
157 * @throws IOException if a disk I/O error or parsing error occurred.
158 */
159 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags,
160 @Nullable AssetsProvider assets) throws IOException {
161 return new ApkAssets(FORMAT_APK, path, flags, assets);
162 }
163
164 /**
165 * Creates a new ApkAssets instance from the given file descriptor.
166 *
167 * Performs a dup of the underlying fd, so you must take care of still closing
168 * the FileDescriptor yourself (and can do that whenever you want).
169 *
170 * @param fd The FileDescriptor of an open, readable APK.
171 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
172 * @param flags flags that change the behavior of loaded apk assets
173 * @param assets The assets provider that overrides the loading of file-based resources
174 * @return a new instance of ApkAssets.
175 * @throws IOException if a disk I/O error or parsing error occurred.
176 */
177 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
178 @NonNull String friendlyName, @PropertyFlags int flags,
179 @Nullable AssetsProvider assets) throws IOException {
180 return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets);
181 }
182
183 /**
184 * Creates a new ApkAssets instance from the given file descriptor.
185 *
186 * Performs a dup of the underlying fd, so you must take care of still closing
187 * the FileDescriptor yourself (and can do that whenever you want).
188 *
189 * @param fd The FileDescriptor of an open, readable APK.
190 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
191 * @param offset The location within the file that the apk starts. This must be 0 if length is
192 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
193 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
194 * if it extends to the end of the file.
195 * @param flags flags that change the behavior of loaded apk assets
196 * @param assets The assets provider that overrides the loading of file-based resources
197 * @return a new instance of ApkAssets.
198 * @throws IOException if a disk I/O error or parsing error occurred.
199 */
200 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
201 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
202 @Nullable AssetsProvider assets)
203 throws IOException {
204 return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets);
205 }
206
207 /**
208 * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
209 * is encoded within the IDMAP.
210 *
211 * @param idmapPath Path to the IDMAP of an overlay APK.
212 * @param flags flags that change the behavior of loaded apk assets
213 * @return a new instance of ApkAssets.
214 * @throws IOException if a disk I/O error or parsing error occurred.
215 */
216 public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
217 @PropertyFlags int flags) throws IOException {
218 return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
219 }
220
221 /**
222 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
223 * for use with a {@link ResourcesProvider}.
224 *
225 * Performs a dup of the underlying fd, so you must take care of still closing
226 * the FileDescriptor yourself (and can do that whenever you want).
227 *
228 * @param fd The FileDescriptor of an open, readable resources.arsc.
229 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
230 * @param flags flags that change the behavior of loaded apk assets
231 * @param assets The assets provider that overrides the loading of file-based resources
232 * @return a new instance of ApkAssets.
233 * @throws IOException if a disk I/O error or parsing error occurred.
234 */
235 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
236 @NonNull String friendlyName, @PropertyFlags int flags,
237 @Nullable AssetsProvider assets) throws IOException {
238 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets);
239 }
240
241 /**
242 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
243 * for use with a {@link ResourcesProvider}.
244 *
245 * Performs a dup of the underlying fd, so you must take care of still closing
246 * the FileDescriptor yourself (and can do that whenever you want).
247 *
248 * @param fd The FileDescriptor of an open, readable resources.arsc.
249 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
250 * @param offset The location within the file that the table starts. This must be 0 if length is
251 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
252 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
253 * if it extends to the end of the file.
254 * @param flags flags that change the behavior of loaded apk assets
255 * @param assets The assets provider that overrides the loading of file-based resources
256 * @return a new instance of ApkAssets.
257 * @throws IOException if a disk I/O error or parsing error occurred.
258 */
259 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
260 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
261 @Nullable AssetsProvider assets) throws IOException {
262 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets);
263 }
264
265 /**
266 * Creates a new ApkAssets instance from the given directory path. The directory should have the
267 * file structure of an APK.
268 *
269 * @param path The path to a directory on disk.
270 * @param flags flags that change the behavior of loaded apk assets
271 * @param assets The assets provider that overrides the loading of file-based resources
272 * @return a new instance of ApkAssets.
273 * @throws IOException if a disk I/O error or parsing error occurred.
274 */
275 public static @NonNull ApkAssets loadFromDir(@NonNull String path,
276 @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
277 return new ApkAssets(FORMAT_DIR, path, flags, assets);
278 }
279
280 /**
281 * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
282 * is required for a lot of APIs, and it's easier to have a non-null reference rather than
283 * tracking a separate identifier.
284 *
285 * @param flags flags that change the behavior of loaded apk assets
286 * @param assets The assets provider that overrides the loading of file-based resources
287 */
288 @NonNull
289 public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
290 @Nullable AssetsProvider assets) {
291 return new ApkAssets(flags, assets);
292 }
293
294 private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
295 @Nullable AssetsProvider assets) throws IOException {
296 Objects.requireNonNull(path, "path");
297 mFlags = flags;
298 mNativePtr = nativeLoad(format, path, flags, assets);
299 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
300 mAssets = assets;
301 }
302
303 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
304 @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
305 throws IOException {
306 Objects.requireNonNull(fd, "fd");
307 Objects.requireNonNull(friendlyName, "friendlyName");
308 mFlags = flags;
309 mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
310 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
311 mAssets = assets;
312 }
313
314 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
315 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
316 @Nullable AssetsProvider assets) throws IOException {
317 Objects.requireNonNull(fd, "fd");
318 Objects.requireNonNull(friendlyName, "friendlyName");
319 mFlags = flags;
320 mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
321 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
322 mAssets = assets;
323 }
324
325 private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
326 mFlags = flags;
327 mNativePtr = nativeLoadEmpty(flags, assets);
328 mStringBlock = null;
329 mAssets = assets;
330 }
331
332 @UnsupportedAppUsage
333 public @NonNull String getAssetPath() {
334 synchronized (this) {
335 return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr));
336 }
337 }
338
339 /** @hide */
340 public @NonNull String getDebugName() {
341 synchronized (this) {
342 return nativeGetDebugName(mNativePtr);
343 }
344 }
345
346 @Nullable
347 CharSequence getStringFromPool(int idx) {
348 if (mStringBlock == null) {
349 return null;
350 }
351
352 synchronized (this) {
353 return mStringBlock.getSequence(idx);
354 }
355 }
356
357 /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */
358 public boolean isForLoader() {
359 return (mFlags & PROPERTY_LOADER) != 0;
360 }
361
362 /**
363 * Returns the assets provider that overrides the loading of assets present in this apk assets.
364 */
365 @Nullable
366 public AssetsProvider getAssetsProvider() {
367 return mAssets;
368 }
369
370 /**
371 * Retrieve a parser for a compiled XML file. This is associated with a single APK and
372 * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
373 * dynamically assigned runtime package IDs.
374 *
375 * @param fileName The path to the file within the APK.
376 * @return An XmlResourceParser.
377 * @throws IOException if the file was not found or an error occurred retrieving it.
378 */
379 public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
380 Objects.requireNonNull(fileName, "fileName");
381 synchronized (this) {
382 long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
383 try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
384 XmlResourceParser parser = block.newParser();
385 // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
386 // which makes newParser always return non-null. But let's be careful.
387 if (parser == null) {
388 throw new AssertionError("block.newParser() returned a null parser");
389 }
390 return parser;
391 }
392 }
393 }
394
395 /** @hide */
396 @Nullable
397 public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
398 synchronized (this) {
399 return nativeGetOverlayableInfo(mNativePtr, overlayableName);
400 }
401 }
402
403 /** @hide */
404 public boolean definesOverlayable() throws IOException {
405 synchronized (this) {
406 return nativeDefinesOverlayable(mNativePtr);
407 }
408 }
409
410 /**
411 * Returns false if the underlying APK was changed since this ApkAssets was loaded.
412 */
413 public boolean isUpToDate() {
414 synchronized (this) {
415 return nativeIsUpToDate(mNativePtr);
416 }
417 }
418
419 @Override
420 public String toString() {
421 return "ApkAssets{path=" + getDebugName() + "}";
422 }
423
424 @Override
425 protected void finalize() throws Throwable {
426 close();
427 }
428
429 /**
430 * Closes this class and the contained {@link #mStringBlock}.
431 */
432 public void close() {
433 synchronized (this) {
434 if (mNativePtr != 0) {
435 if (mStringBlock != null) {
436 mStringBlock.close();
437 }
438 nativeDestroy(mNativePtr);
439 mNativePtr = 0;
440 }
441 }
442 }
443
444 void dump(PrintWriter pw, String prefix) {
445 pw.println(prefix + "class=" + getClass());
446 pw.println(prefix + "debugName=" + getDebugName());
447 pw.println(prefix + "assetPath=" + getAssetPath());
448 }
449
450 private static native long nativeLoad(@FormatType int format, @NonNull String path,
451 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
452 private static native long nativeLoadEmpty(@PropertyFlags int flags,
453 @Nullable AssetsProvider asset);
454 private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd,
455 @NonNull String friendlyName, @PropertyFlags int flags,
456 @Nullable AssetsProvider asset) throws IOException;
457 private static native long nativeLoadFdOffsets(@FormatType int format,
458 @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length,
459 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
460 private static native void nativeDestroy(long ptr);
461 private static native @NonNull String nativeGetAssetPath(long ptr);
462 private static native @NonNull String nativeGetDebugName(long ptr);
463 private static native long nativeGetStringBlock(long ptr);
464 @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
465 private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
466 private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
467 String overlayableName) throws IOException;
468 private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
469}