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