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