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.NonNull;
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 import com.android.internal.annotations.GuardedBy;
22 import com.android.internal.util.Preconditions;
23 
24 import java.io.FileDescriptor;
25 import java.io.IOException;
26 
27 /**
28  * The loaded, immutable, in-memory representation of an APK.
29  *
30  * The main implementation is native C++ and there is very little API surface exposed here. The APK
31  * is mainly accessed via {@link AssetManager}.
32  *
33  * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
34  * making the creation of AssetManagers very cheap.
35  * @hide
36  */
37 public final class ApkAssets {
38     @GuardedBy("this") private final long mNativePtr;
39     @GuardedBy("this") private final StringBlock mStringBlock;
40 
41     @GuardedBy("this") private boolean mOpen = true;
42 
43     /**
44      * Creates a new ApkAssets instance from the given path on disk.
45      *
46      * @param path The path to an APK on disk.
47      * @return a new instance of ApkAssets.
48      * @throws IOException if a disk I/O error or parsing error occurred.
49      */
loadFromPath(@onNull String path)50     public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
51         return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
52     }
53 
54     /**
55      * Creates a new ApkAssets instance from the given path on disk.
56      *
57      * @param path The path to an APK on disk.
58      * @param system When true, the APK is loaded as a system APK (framework).
59      * @return a new instance of ApkAssets.
60      * @throws IOException if a disk I/O error or parsing error occurred.
61      */
loadFromPath(@onNull String path, boolean system)62     public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
63             throws IOException {
64         return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
65     }
66 
67     /**
68      * Creates a new ApkAssets instance from the given path on disk.
69      *
70      * @param path The path to an APK on disk.
71      * @param system When true, the APK is loaded as a system APK (framework).
72      * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
73      *                           loaded as a shared library.
74      * @return a new instance of ApkAssets.
75      * @throws IOException if a disk I/O error or parsing error occurred.
76      */
loadFromPath(@onNull String path, boolean system, boolean forceSharedLibrary)77     public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
78             boolean forceSharedLibrary) throws IOException {
79         return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
80     }
81 
82     /**
83      * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications.
84      *
85      * Performs a dup of the underlying fd, so you must take care of still closing
86      * the FileDescriptor yourself (and can do that whenever you want).
87      *
88      * @param fd The FileDescriptor of an open, readable APK.
89      * @param friendlyName The friendly name used to identify this ApkAssets when logging.
90      * @param system When true, the APK is loaded as a system APK (framework).
91      * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
92      *                           loaded as a shared library.
93      * @return a new instance of ApkAssets.
94      * @throws IOException if a disk I/O error or parsing error occurred.
95      */
loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)96     public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
97             @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
98             throws IOException {
99         return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
100     }
101 
102     /**
103      * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
104      * is encoded within the IDMAP.
105      *
106      * @param idmapPath Path to the IDMAP of an overlay APK.
107      * @param system When true, the APK is loaded as a system APK (framework).
108      * @return a new instance of ApkAssets.
109      * @throws IOException if a disk I/O error or parsing error occurred.
110      */
loadOverlayFromPath(@onNull String idmapPath, boolean system)111     public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
112             throws IOException {
113         return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
114     }
115 
ApkAssets(@onNull String path, boolean system, boolean forceSharedLib, boolean overlay)116     private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
117             throws IOException {
118         Preconditions.checkNotNull(path, "path");
119         mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
120         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
121     }
122 
ApkAssets(@onNull FileDescriptor fd, @NonNull String friendlyName, boolean system, boolean forceSharedLib)123     private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
124             boolean forceSharedLib) throws IOException {
125         Preconditions.checkNotNull(fd, "fd");
126         Preconditions.checkNotNull(friendlyName, "friendlyName");
127         mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
128         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
129     }
130 
131     @UnsupportedAppUsage
getAssetPath()132     public @NonNull String getAssetPath() {
133         synchronized (this) {
134             return nativeGetAssetPath(mNativePtr);
135         }
136     }
137 
getStringFromPool(int idx)138     CharSequence getStringFromPool(int idx) {
139         synchronized (this) {
140             return mStringBlock.get(idx);
141         }
142     }
143 
144     /**
145      * Retrieve a parser for a compiled XML file. This is associated with a single APK and
146      * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
147      * dynamically assigned runtime package IDs.
148      *
149      * @param fileName The path to the file within the APK.
150      * @return An XmlResourceParser.
151      * @throws IOException if the file was not found or an error occurred retrieving it.
152      */
openXml(@onNull String fileName)153     public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
154         Preconditions.checkNotNull(fileName, "fileName");
155         synchronized (this) {
156             long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
157             try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
158                 XmlResourceParser parser = block.newParser();
159                 // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
160                 // which makes newParser always return non-null. But let's be paranoid.
161                 if (parser == null) {
162                     throw new AssertionError("block.newParser() returned a null parser");
163                 }
164                 return parser;
165             }
166         }
167     }
168 
169     /**
170      * Returns false if the underlying APK was changed since this ApkAssets was loaded.
171      */
isUpToDate()172     public boolean isUpToDate() {
173         synchronized (this) {
174             return nativeIsUpToDate(mNativePtr);
175         }
176     }
177 
178     @Override
toString()179     public String toString() {
180         return "ApkAssets{path=" + getAssetPath() + "}";
181     }
182 
183     @Override
finalize()184     protected void finalize() throws Throwable {
185         close();
186     }
187 
188     /**
189      * Closes this class and the contained {@link #mStringBlock}.
190      */
close()191     public void close() throws Throwable {
192         synchronized (this) {
193             if (mOpen) {
194                 mOpen = false;
195                 mStringBlock.close();
196                 nativeDestroy(mNativePtr);
197             }
198         }
199     }
200 
nativeLoad( @onNull String path, boolean system, boolean forceSharedLib, boolean overlay)201     private static native long nativeLoad(
202             @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
203             throws IOException;
nativeLoadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, boolean system, boolean forceSharedLib)204     private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
205             @NonNull String friendlyName, boolean system, boolean forceSharedLib)
206             throws IOException;
nativeDestroy(long ptr)207     private static native void nativeDestroy(long ptr);
nativeGetAssetPath(long ptr)208     private static native @NonNull String nativeGetAssetPath(long ptr);
nativeGetStringBlock(long ptr)209     private static native long nativeGetStringBlock(long ptr);
nativeIsUpToDate(long ptr)210     private static native boolean nativeIsUpToDate(long ptr);
nativeOpenXml(long ptr, @NonNull String fileName)211     private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
212 }
213