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