blob: 078d175b9a80f811986ab8aa6bb614d98e63f117 [file] [log] [blame]
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001/*
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
Ryan Mitchellef40d2e2020-03-11 10:26:08 -070018import android.annotation.IntDef;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080019import android.annotation.NonNull;
Winson9947f1e2019-08-16 10:20:39 -070020import android.annotation.Nullable;
Artur Satayeve23a0eb2019-12-10 17:47:52 +000021import android.compat.annotation.UnsupportedAppUsage;
Winsond9d17362019-10-02 12:41:29 -070022import android.content.om.OverlayableInfo;
Winson9947f1e2019-08-16 10:20:39 -070023import android.content.res.loader.ResourcesProvider;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080024
25import com.android.internal.annotations.GuardedBy;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080026
27import java.io.FileDescriptor;
28import java.io.IOException;
Ryan Mitchellef40d2e2020-03-11 10:26:08 -070029import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
Daulet Zhanguzina2044e12019-12-30 16:34:59 +000031import java.util.Objects;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080032
33/**
34 * The loaded, immutable, in-memory representation of an APK.
35 *
36 * The main implementation is native C++ and there is very little API surface exposed here. The APK
37 * is mainly accessed via {@link AssetManager}.
38 *
39 * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
40 * making the creation of AssetManagers very cheap.
41 * @hide
42 */
43public final class ApkAssets {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -070044
45 /**
46 * The apk assets contains framework resource values specified by the system.
47 * This allows some functions to filter out this package when computing what
48 * configurations/resources are available.
49 */
50 public static final int PROPERTY_SYSTEM = 1 << 0;
51
52 /**
53 * The apk assets is a shared library or was loaded as a shared library by force.
54 * The package ids of dynamic apk assets are assigned at runtime instead of compile time.
55 */
56 public static final int PROPERTY_DYNAMIC = 1 << 1;
57
58 /**
59 * The apk assets has been loaded dynamically using a {@link ResourcesProvider}.
60 * Loader apk assets overlay resources like RROs except they are not backed by an idmap.
61 */
62 public static final int PROPERTY_LOADER = 1 << 2;
63
64 /**
65 * The apk assets is a RRO.
66 * An RRO overlays resource values of its target package.
67 */
68 private static final int PROPERTY_OVERLAY = 1 << 3;
69
70 /** Flags that change the behavior of loaded apk assets. */
71 @IntDef(prefix = { "PROPERTY_" }, value = {
72 PROPERTY_SYSTEM,
73 PROPERTY_DYNAMIC,
74 PROPERTY_LOADER,
75 PROPERTY_OVERLAY,
76 })
77 @Retention(RetentionPolicy.SOURCE)
78 public @interface PropertyFlags {}
79
80 /** The path used to load the apk assets represents an APK file. */
81 private static final int FORMAT_APK = 0;
82
83 /** The path used to load the apk assets represents an idmap file. */
84 private static final int FORMAT_IDMAP = 1;
85
86 /** The path used to load the apk assets represents an resources.arsc file. */
87 private static final int FORMAT_ARSC = 2;
88
Ryan Mitchellc07aa702020-03-10 13:49:12 -070089 /** the path used to load the apk assets represents a directory. */
90 private static final int FORMAT_DIR = 3;
91
Ryan Mitchellef40d2e2020-03-11 10:26:08 -070092 // Format types that change how the apk assets are loaded.
93 @IntDef(prefix = { "FORMAT_" }, value = {
94 FORMAT_APK,
95 FORMAT_IDMAP,
96 FORMAT_ARSC,
Ryan Mitchellc07aa702020-03-10 13:49:12 -070097 FORMAT_DIR
Ryan Mitchellef40d2e2020-03-11 10:26:08 -070098 })
99 @Retention(RetentionPolicy.SOURCE)
100 public @interface FormatType {}
101
102 @GuardedBy("this")
103 private final long mNativePtr;
Winson9947f1e2019-08-16 10:20:39 -0700104
105 @Nullable
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700106 @GuardedBy("this")
107 private final StringBlock mStringBlock;
Winson7a3d82a2019-03-07 12:54:24 -0800108
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700109 @GuardedBy("this")
110 private boolean mOpen = true;
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800111
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700112 @PropertyFlags
113 private final int mFlags;
Winson9947f1e2019-08-16 10:20:39 -0700114
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800115 /**
116 * Creates a new ApkAssets instance from the given path on disk.
117 *
118 * @param path The path to an APK on disk.
119 * @return a new instance of ApkAssets.
120 * @throws IOException if a disk I/O error or parsing error occurred.
121 */
122 public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700123 return loadFromPath(path, 0 /* flags */);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800124 }
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.
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700130 * @param flags flags that change the behavior of loaded apk assets
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800131 * @return a new instance of ApkAssets.
132 * @throws IOException if a disk I/O error or parsing error occurred.
133 */
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700134 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800135 throws IOException {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700136 return new ApkAssets(FORMAT_APK, path, flags);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800137 }
138
139 /**
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700140 * Creates a new ApkAssets instance from the given file descriptor.
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800141 *
142 * Performs a dup of the underlying fd, so you must take care of still closing
143 * the FileDescriptor yourself (and can do that whenever you want).
144 *
145 * @param fd The FileDescriptor of an open, readable APK.
146 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700147 * @param flags flags that change the behavior of loaded apk assets
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800148 * @return a new instance of ApkAssets.
149 * @throws IOException if a disk I/O error or parsing error occurred.
150 */
151 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700152 @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
153 return new ApkAssets(FORMAT_APK, fd, friendlyName, flags);
154 }
155
156 /**
157 * Creates a new ApkAssets instance from the given file descriptor.
158 *
159 * Performs a dup of the underlying fd, so you must take care of still closing
160 * the FileDescriptor yourself (and can do that whenever you want).
161 *
162 * @param fd The FileDescriptor of an open, readable APK.
163 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
164 * @param offset The location within the file that the apk starts. This must be 0 if length is
165 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
166 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
167 * if it extends to the end of the file.
168 * @param flags flags that change the behavior of loaded apk assets
169 * @return a new instance of ApkAssets.
170 * @throws IOException if a disk I/O error or parsing error occurred.
171 */
172 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
173 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800174 throws IOException {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700175 return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800176 }
177
178 /**
179 * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
180 * is encoded within the IDMAP.
181 *
182 * @param idmapPath Path to the IDMAP of an overlay APK.
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700183 * @param flags flags that change the behavior of loaded apk assets
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800184 * @return a new instance of ApkAssets.
185 * @throws IOException if a disk I/O error or parsing error occurred.
186 */
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700187 public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
188 @PropertyFlags int flags) throws IOException {
189 return new ApkAssets(FORMAT_IDMAP, idmapPath, flags);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800190 }
191
Winson9947f1e2019-08-16 10:20:39 -0700192 /**
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700193 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
Winson9947f1e2019-08-16 10:20:39 -0700194 * for use with a {@link ResourcesProvider}.
195 *
196 * Performs a dup of the underlying fd, so you must take care of still closing
197 * the FileDescriptor yourself (and can do that whenever you want).
198 *
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700199 * @param fd The FileDescriptor of an open, readable resources.arsc.
200 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
201 * @param flags flags that change the behavior of loaded apk assets
Winson9947f1e2019-08-16 10:20:39 -0700202 * @return a new instance of ApkAssets.
203 * @throws IOException if a disk I/O error or parsing error occurred.
204 */
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700205 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
206 @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
207 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags);
208 }
209
210 /**
211 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
212 * for use with a {@link ResourcesProvider}.
213 *
214 * Performs a dup of the underlying fd, so you must take care of still closing
215 * the FileDescriptor yourself (and can do that whenever you want).
216 *
217 * @param fd The FileDescriptor of an open, readable resources.arsc.
218 * @param friendlyName The friendly name used to identify this ApkAssets when logging.
219 * @param offset The location within the file that the table starts. This must be 0 if length is
220 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
221 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
222 * if it extends to the end of the file.
223 * @param flags flags that change the behavior of loaded apk assets
224 * @return a new instance of ApkAssets.
225 * @throws IOException if a disk I/O error or parsing error occurred.
226 */
227 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
228 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
Winson9947f1e2019-08-16 10:20:39 -0700229 throws IOException {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700230 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags);
Winson9947f1e2019-08-16 10:20:39 -0700231 }
232
233 /**
Ryan Mitchellc07aa702020-03-10 13:49:12 -0700234 * Creates a new ApkAssets instance from the given directory path. The directory should have the
235 * file structure of an APK.
236 *
237 * @param path The path to a directory on disk.
238 * @param flags flags that change the behavior of loaded apk assets
239 * @return a new instance of ApkAssets.
240 * @throws IOException if a disk I/O error or parsing error occurred.
241 */
242 public static @NonNull ApkAssets loadFromDir(@NonNull String path,
243 @PropertyFlags int flags) throws IOException {
244 return new ApkAssets(FORMAT_DIR, path, flags);
245 }
246
247 /**
Winson9947f1e2019-08-16 10:20:39 -0700248 * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
249 * is required for a lot of APIs, and it's easier to have a non-null reference rather than
250 * tracking a separate identifier.
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700251 *
252 * @param flags flags that change the behavior of loaded apk assets
Winson9947f1e2019-08-16 10:20:39 -0700253 */
254 @NonNull
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700255 public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags) {
256 return new ApkAssets(flags);
Winson9947f1e2019-08-16 10:20:39 -0700257 }
258
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700259 private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags)
260 throws IOException {
Daulet Zhanguzina2044e12019-12-30 16:34:59 +0000261 Objects.requireNonNull(path, "path");
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700262 mFlags = flags;
263 mNativePtr = nativeLoad(format, path, flags);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800264 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
265 }
266
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700267 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
268 @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
Daulet Zhanguzina2044e12019-12-30 16:34:59 +0000269 Objects.requireNonNull(fd, "fd");
270 Objects.requireNonNull(friendlyName, "friendlyName");
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700271 mFlags = flags;
272 mNativePtr = nativeLoadFd(format, fd, friendlyName, flags);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800273 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
274 }
275
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700276 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
277 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
278 throws IOException {
279 Objects.requireNonNull(fd, "fd");
280 Objects.requireNonNull(friendlyName, "friendlyName");
281 mFlags = flags;
282 mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags);
283 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
284 }
285
286 private ApkAssets(@PropertyFlags int flags) {
287 mFlags = flags;
288 mNativePtr = nativeLoadEmpty(flags);
289 mStringBlock = null;
290 }
291
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100292 @UnsupportedAppUsage
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800293 public @NonNull String getAssetPath() {
294 synchronized (this) {
295 return nativeGetAssetPath(mNativePtr);
296 }
297 }
298
299 CharSequence getStringFromPool(int idx) {
Winson9947f1e2019-08-16 10:20:39 -0700300 if (mStringBlock == null) {
301 return null;
302 }
303
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800304 synchronized (this) {
305 return mStringBlock.get(idx);
306 }
307 }
308
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700309 /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */
Winson9947f1e2019-08-16 10:20:39 -0700310 public boolean isForLoader() {
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700311 return (mFlags & PROPERTY_LOADER) != 0;
Winson9947f1e2019-08-16 10:20:39 -0700312 }
313
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800314 /**
315 * Retrieve a parser for a compiled XML file. This is associated with a single APK and
316 * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
317 * dynamically assigned runtime package IDs.
318 *
319 * @param fileName The path to the file within the APK.
320 * @return An XmlResourceParser.
321 * @throws IOException if the file was not found or an error occurred retrieving it.
322 */
323 public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
Daulet Zhanguzina2044e12019-12-30 16:34:59 +0000324 Objects.requireNonNull(fileName, "fileName");
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800325 synchronized (this) {
326 long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
327 try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
328 XmlResourceParser parser = block.newParser();
329 // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
330 // which makes newParser always return non-null. But let's be paranoid.
331 if (parser == null) {
332 throw new AssertionError("block.newParser() returned a null parser");
333 }
334 return parser;
335 }
336 }
337 }
338
Winsond9d17362019-10-02 12:41:29 -0700339 /** @hide */
340 @Nullable
341 public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
342 return nativeGetOverlayableInfo(mNativePtr, overlayableName);
343 }
344
345 /** @hide */
346 public boolean definesOverlayable() throws IOException {
347 return nativeDefinesOverlayable(mNativePtr);
348 }
349
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800350 /**
351 * Returns false if the underlying APK was changed since this ApkAssets was loaded.
352 */
353 public boolean isUpToDate() {
354 synchronized (this) {
355 return nativeIsUpToDate(mNativePtr);
356 }
357 }
358
359 @Override
360 public String toString() {
361 return "ApkAssets{path=" + getAssetPath() + "}";
362 }
363
364 @Override
365 protected void finalize() throws Throwable {
Winson7a3d82a2019-03-07 12:54:24 -0800366 close();
367 }
368
369 /**
370 * Closes this class and the contained {@link #mStringBlock}.
371 */
Ryan Mitchell4043ca72019-06-03 16:11:24 -0700372 public void close() {
Winson7a3d82a2019-03-07 12:54:24 -0800373 synchronized (this) {
374 if (mOpen) {
375 mOpen = false;
Winson9947f1e2019-08-16 10:20:39 -0700376 if (mStringBlock != null) {
377 mStringBlock.close();
378 }
Winson7a3d82a2019-03-07 12:54:24 -0800379 nativeDestroy(mNativePtr);
380 }
381 }
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800382 }
383
Ryan Mitchellef40d2e2020-03-11 10:26:08 -0700384 private static native long nativeLoad(@FormatType int format, @NonNull String path,
385 @PropertyFlags int flags) throws IOException;
386 private static native long nativeLoadEmpty(@PropertyFlags int flags);
387 private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd,
388 @NonNull String friendlyName, @PropertyFlags int flags) throws IOException;
389 private static native long nativeLoadFdOffsets(@FormatType int format,
390 @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length,
391 @PropertyFlags int flags) throws IOException;
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800392 private static native void nativeDestroy(long ptr);
393 private static native @NonNull String nativeGetAssetPath(long ptr);
394 private static native long nativeGetStringBlock(long ptr);
395 private static native boolean nativeIsUpToDate(long ptr);
396 private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
Winsond9d17362019-10-02 12:41:29 -0700397 private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
398 String overlayableName) throws IOException;
399 private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800400}