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     /** 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      */
loadFromPath(@onNull String path)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      */
loadFromPath(@onNull String path, @PropertyFlags int flags)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      */
loadFromPath(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)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      */
loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)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      */
loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)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      */
loadOverlayFromPath(@onNull String idmapPath, @PropertyFlags int flags)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      */
loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)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      */
loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)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      */
loadFromDir(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)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
loadEmptyForLoader(@ropertyFlags int flags, @Nullable AssetsProvider assets)289     public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
290             @Nullable AssetsProvider assets) {
291         return new ApkAssets(flags, assets);
292     }
293 
ApkAssets(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)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 
ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)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 
ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)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 
ApkAssets(@ropertyFlags int flags, @Nullable AssetsProvider assets)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
getAssetPath()333     public @NonNull String getAssetPath() {
334         synchronized (this) {
335             return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr));
336         }
337     }
338 
339     /** @hide */
getDebugName()340     public @NonNull String getDebugName() {
341         synchronized (this) {
342             return nativeGetDebugName(mNativePtr);
343         }
344     }
345 
346     @Nullable
getStringFromPool(int idx)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}. */
isForLoader()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
getAssetsProvider()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      */
openXml(@onNull String fileName)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
getOverlayableInfo(String overlayableName)397     public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
398         synchronized (this) {
399             return nativeGetOverlayableInfo(mNativePtr, overlayableName);
400         }
401     }
402 
403     /** @hide */
definesOverlayable()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      */
isUpToDate()413     public boolean isUpToDate() {
414         synchronized (this) {
415             return nativeIsUpToDate(mNativePtr);
416         }
417     }
418 
419     @Override
toString()420     public String toString() {
421         return "ApkAssets{path=" + getDebugName() + "}";
422     }
423 
424     @Override
finalize()425     protected void finalize() throws Throwable {
426         close();
427     }
428 
429     /**
430      * Closes this class and the contained {@link #mStringBlock}.
431      */
close()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 
dump(PrintWriter pw, String prefix)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 
nativeLoad(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset)450     private static native long nativeLoad(@FormatType int format, @NonNull String path,
451             @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
nativeLoadEmpty(@ropertyFlags int flags, @Nullable AssetsProvider asset)452     private static native long nativeLoadEmpty(@PropertyFlags int flags,
453             @Nullable AssetsProvider asset);
nativeLoadFd(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @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;
nativeLoadFdOffsets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider asset)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;
nativeDestroy(long ptr)460     private static native void nativeDestroy(long ptr);
nativeGetAssetPath(long ptr)461     private static native @NonNull String nativeGetAssetPath(long ptr);
nativeGetDebugName(long ptr)462     private static native @NonNull String nativeGetDebugName(long ptr);
nativeGetStringBlock(long ptr)463     private static native long nativeGetStringBlock(long ptr);
nativeIsUpToDate(long ptr)464     @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
nativeOpenXml(long ptr, @NonNull String fileName)465     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
nativeGetOverlayableInfo(long ptr, String overlayableName)466     private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
467             String overlayableName) throws IOException;
nativeDefinesOverlayable(long ptr)468     private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
469 }
470