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