Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 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; |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 19 | import android.annotation.Nullable; |
Mathew Inwood | 5c0d354 | 2018-08-14 13:54:31 +0100 | [diff] [blame] | 20 | import android.annotation.UnsupportedAppUsage; |
Winson | d9d1736 | 2019-10-02 12:41:29 -0700 | [diff] [blame] | 21 | import android.content.om.OverlayableInfo; |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 22 | import android.content.res.loader.ResourcesProvider; |
| 23 | import android.text.TextUtils; |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 24 | |
| 25 | import com.android.internal.annotations.GuardedBy; |
| 26 | import com.android.internal.util.Preconditions; |
| 27 | |
| 28 | import java.io.FileDescriptor; |
| 29 | import java.io.IOException; |
| 30 | |
| 31 | /** |
| 32 | * The loaded, immutable, in-memory representation of an APK. |
| 33 | * |
| 34 | * The main implementation is native C++ and there is very little API surface exposed here. The APK |
| 35 | * is mainly accessed via {@link AssetManager}. |
| 36 | * |
| 37 | * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, |
| 38 | * making the creation of AssetManagers very cheap. |
| 39 | * @hide |
| 40 | */ |
| 41 | public final class ApkAssets { |
| 42 | @GuardedBy("this") private final long mNativePtr; |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 43 | |
| 44 | @Nullable |
Winson | 7a3d82a | 2019-03-07 12:54:24 -0800 | [diff] [blame] | 45 | @GuardedBy("this") private final StringBlock mStringBlock; |
| 46 | |
| 47 | @GuardedBy("this") private boolean mOpen = true; |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 48 | |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 49 | private final boolean mForLoader; |
| 50 | |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 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 | * @return a new instance of ApkAssets. |
| 56 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 57 | */ |
| 58 | public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 59 | return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/, |
| 60 | false /*arscOnly*/, false /*forLoader*/); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Creates a new ApkAssets instance from the given path on disk. |
| 65 | * |
| 66 | * @param path The path to an APK on disk. |
| 67 | * @param system When true, the APK is loaded as a system APK (framework). |
| 68 | * @return a new instance of ApkAssets. |
| 69 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 70 | */ |
| 71 | public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) |
| 72 | throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 73 | return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/, |
| 74 | false /*arscOnly*/, false /*forLoader*/); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Creates a new ApkAssets instance from the given path on disk. |
| 79 | * |
| 80 | * @param path The path to an APK on disk. |
| 81 | * @param system When true, the APK is loaded as a system APK (framework). |
| 82 | * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are |
| 83 | * loaded as a shared library. |
| 84 | * @return a new instance of ApkAssets. |
| 85 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 86 | */ |
| 87 | public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, |
| 88 | boolean forceSharedLibrary) throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 89 | return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/, |
| 90 | false /*arscOnly*/, false /*forLoader*/); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. |
| 95 | * |
| 96 | * Performs a dup of the underlying fd, so you must take care of still closing |
| 97 | * the FileDescriptor yourself (and can do that whenever you want). |
| 98 | * |
| 99 | * @param fd The FileDescriptor of an open, readable APK. |
| 100 | * @param friendlyName The friendly name used to identify this ApkAssets when logging. |
| 101 | * @param system When true, the APK is loaded as a system APK (framework). |
| 102 | * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are |
| 103 | * loaded as a shared library. |
| 104 | * @return a new instance of ApkAssets. |
| 105 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 106 | */ |
| 107 | public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, |
| 108 | @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) |
| 109 | throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 110 | return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/, |
| 111 | false /*forLoader*/); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path |
| 116 | * is encoded within the IDMAP. |
| 117 | * |
| 118 | * @param idmapPath Path to the IDMAP of an overlay APK. |
| 119 | * @param system When true, the APK is loaded as a system APK (framework). |
| 120 | * @return a new instance of ApkAssets. |
| 121 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 122 | */ |
| 123 | public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) |
| 124 | throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 125 | return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/, |
| 126 | false /*arscOnly*/, false /*forLoader*/); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 127 | } |
| 128 | |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 129 | /** |
| 130 | * Creates a new ApkAssets instance from the given path on disk for use with a |
| 131 | * {@link ResourcesProvider}. |
| 132 | * |
| 133 | * @param path The path to an APK on disk. |
| 134 | * @return a new instance of ApkAssets. |
| 135 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 136 | */ |
| 137 | public static @NonNull ApkAssets loadApkForLoader(@NonNull String path) |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 138 | throws IOException { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 139 | return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/, |
| 140 | false /*overlay*/, false /*arscOnly*/, true /*forLoader*/); |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Creates a new ApkAssets instance from the given file descriptor for use with a |
| 145 | * {@link ResourcesProvider}. |
| 146 | * |
| 147 | * Performs a dup of the underlying fd, so you must take care of still closing |
| 148 | * the FileDescriptor yourself (and can do that whenever you want). |
| 149 | * |
| 150 | * @param fd The FileDescriptor of an open, readable APK. |
| 151 | * @return a new instance of ApkAssets. |
| 152 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 153 | */ |
| 154 | @NonNull |
| 155 | public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException { |
| 156 | return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()), |
| 157 | false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/); |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Creates a new ApkAssets instance from the given file descriptor representing an ARSC |
| 162 | * for use with a {@link ResourcesProvider}. |
| 163 | * |
| 164 | * Performs a dup of the underlying fd, so you must take care of still closing |
| 165 | * the FileDescriptor yourself (and can do that whenever you want). |
| 166 | * |
| 167 | * @param fd The FileDescriptor of an open, readable .arsc. |
| 168 | * @return a new instance of ApkAssets. |
| 169 | * @throws IOException if a disk I/O error or parsing error occurred. |
| 170 | */ |
| 171 | public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd) |
| 172 | throws IOException { |
| 173 | return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()), |
| 174 | false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/); |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence |
| 179 | * is required for a lot of APIs, and it's easier to have a non-null reference rather than |
| 180 | * tracking a separate identifier. |
| 181 | */ |
| 182 | @NonNull |
| 183 | public static ApkAssets loadEmptyForLoader() { |
| 184 | return new ApkAssets(true); |
| 185 | } |
| 186 | |
| 187 | private ApkAssets(boolean forLoader) { |
| 188 | mForLoader = forLoader; |
| 189 | mNativePtr = nativeLoadEmpty(forLoader); |
| 190 | mStringBlock = null; |
| 191 | } |
| 192 | |
| 193 | private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay, |
| 194 | boolean arscOnly, boolean forLoader) throws IOException { |
| 195 | mForLoader = forLoader; |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 196 | Preconditions.checkNotNull(path, "path"); |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 197 | mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader) |
| 198 | : nativeLoad(path, system, forceSharedLib, overlay, forLoader); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 199 | mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); |
| 200 | } |
| 201 | |
| 202 | private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 203 | boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException { |
| 204 | mForLoader = forLoader; |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 205 | Preconditions.checkNotNull(fd, "fd"); |
| 206 | Preconditions.checkNotNull(friendlyName, "friendlyName"); |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 207 | mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader) |
| 208 | : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 209 | mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); |
| 210 | } |
| 211 | |
Mathew Inwood | 5c0d354 | 2018-08-14 13:54:31 +0100 | [diff] [blame] | 212 | @UnsupportedAppUsage |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 213 | public @NonNull String getAssetPath() { |
| 214 | synchronized (this) { |
| 215 | return nativeGetAssetPath(mNativePtr); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | CharSequence getStringFromPool(int idx) { |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 220 | if (mStringBlock == null) { |
| 221 | return null; |
| 222 | } |
| 223 | |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 224 | synchronized (this) { |
| 225 | return mStringBlock.get(idx); |
| 226 | } |
| 227 | } |
| 228 | |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 229 | public boolean isForLoader() { |
| 230 | return mForLoader; |
| 231 | } |
| 232 | |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 233 | /** |
| 234 | * Retrieve a parser for a compiled XML file. This is associated with a single APK and |
| 235 | * <em>NOT</em> a full AssetManager. This means that shared-library references will not be |
| 236 | * dynamically assigned runtime package IDs. |
| 237 | * |
| 238 | * @param fileName The path to the file within the APK. |
| 239 | * @return An XmlResourceParser. |
| 240 | * @throws IOException if the file was not found or an error occurred retrieving it. |
| 241 | */ |
| 242 | public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { |
| 243 | Preconditions.checkNotNull(fileName, "fileName"); |
| 244 | synchronized (this) { |
| 245 | long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); |
| 246 | try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { |
| 247 | XmlResourceParser parser = block.newParser(); |
| 248 | // If nativeOpenXml doesn't throw, it will always return a valid native pointer, |
| 249 | // which makes newParser always return non-null. But let's be paranoid. |
| 250 | if (parser == null) { |
| 251 | throw new AssertionError("block.newParser() returned a null parser"); |
| 252 | } |
| 253 | return parser; |
| 254 | } |
| 255 | } |
| 256 | } |
| 257 | |
Winson | d9d1736 | 2019-10-02 12:41:29 -0700 | [diff] [blame] | 258 | /** @hide */ |
| 259 | @Nullable |
| 260 | public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException { |
| 261 | return nativeGetOverlayableInfo(mNativePtr, overlayableName); |
| 262 | } |
| 263 | |
| 264 | /** @hide */ |
| 265 | public boolean definesOverlayable() throws IOException { |
| 266 | return nativeDefinesOverlayable(mNativePtr); |
| 267 | } |
| 268 | |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 269 | /** |
| 270 | * Returns false if the underlying APK was changed since this ApkAssets was loaded. |
| 271 | */ |
| 272 | public boolean isUpToDate() { |
| 273 | synchronized (this) { |
| 274 | return nativeIsUpToDate(mNativePtr); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | @Override |
| 279 | public String toString() { |
| 280 | return "ApkAssets{path=" + getAssetPath() + "}"; |
| 281 | } |
| 282 | |
| 283 | @Override |
| 284 | protected void finalize() throws Throwable { |
Winson | 7a3d82a | 2019-03-07 12:54:24 -0800 | [diff] [blame] | 285 | close(); |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Closes this class and the contained {@link #mStringBlock}. |
| 290 | */ |
Ryan Mitchell | 4043ca7 | 2019-06-03 16:11:24 -0700 | [diff] [blame] | 291 | public void close() { |
Winson | 7a3d82a | 2019-03-07 12:54:24 -0800 | [diff] [blame] | 292 | synchronized (this) { |
| 293 | if (mOpen) { |
| 294 | mOpen = false; |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 295 | if (mStringBlock != null) { |
| 296 | mStringBlock.close(); |
| 297 | } |
Winson | 7a3d82a | 2019-03-07 12:54:24 -0800 | [diff] [blame] | 298 | nativeDestroy(mNativePtr); |
| 299 | } |
| 300 | } |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 301 | } |
| 302 | |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 303 | private static native long nativeLoad(@NonNull String path, boolean system, |
| 304 | boolean forceSharedLib, boolean overlay, boolean forLoader) |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 305 | throws IOException; |
| 306 | private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 307 | @NonNull String friendlyName, boolean system, boolean forceSharedLib, |
| 308 | boolean forLoader) |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 309 | throws IOException; |
Winson | 9947f1e | 2019-08-16 10:20:39 -0700 | [diff] [blame] | 310 | private static native long nativeLoadArsc(@NonNull String path, boolean forLoader) |
| 311 | throws IOException; |
| 312 | private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd, |
| 313 | @NonNull String friendlyName, boolean forLoader) throws IOException; |
| 314 | private static native long nativeLoadEmpty(boolean forLoader); |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 315 | private static native void nativeDestroy(long ptr); |
| 316 | private static native @NonNull String nativeGetAssetPath(long ptr); |
| 317 | private static native long nativeGetStringBlock(long ptr); |
| 318 | private static native boolean nativeIsUpToDate(long ptr); |
| 319 | private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; |
Winson | d9d1736 | 2019-10-02 12:41:29 -0700 | [diff] [blame] | 320 | private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, |
| 321 | String overlayableName) throws IOException; |
| 322 | private static native boolean nativeDefinesOverlayable(long ptr) throws IOException; |
Adam Lesinski | bebfcc4 | 2018-02-12 14:27:46 -0800 | [diff] [blame] | 323 | } |