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 import android.ravenwood.annotation.RavenwoodClassLoadHook; 26 import android.ravenwood.annotation.RavenwoodKeepWholeClass; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import dalvik.annotation.optimization.CriticalNative; 33 34 import java.io.FileDescriptor; 35 import java.io.IOException; 36 import java.io.PrintWriter; 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.Objects; 40 41 /** 42 * The loaded, immutable, in-memory representation of an APK. 43 * 44 * The main implementation is native C++ and there is very little API surface exposed here. The APK 45 * is mainly accessed via {@link AssetManager}. 46 * 47 * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, 48 * making the creation of AssetManagers very cheap. 49 * @hide 50 */ 51 @RavenwoodKeepWholeClass 52 @RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) 53 public final class ApkAssets { 54 private static final boolean DEBUG = false; 55 56 /** 57 * The apk assets contains framework resource values specified by the system. 58 * This allows some functions to filter out this package when computing what 59 * configurations/resources are available. 60 */ 61 public static final int PROPERTY_SYSTEM = 1 << 0; 62 63 /** 64 * The apk assets is a shared library or was loaded as a shared library by force. 65 * The package ids of dynamic apk assets are assigned at runtime instead of compile time. 66 */ 67 public static final int PROPERTY_DYNAMIC = 1 << 1; 68 69 /** 70 * The apk assets has been loaded dynamically using a {@link ResourcesProvider}. 71 * Loader apk assets overlay resources like RROs except they are not backed by an idmap. 72 */ 73 public static final int PROPERTY_LOADER = 1 << 2; 74 75 /** 76 * The apk assets is a RRO. 77 * An RRO overlays resource values of its target package. 78 */ 79 private static final int PROPERTY_OVERLAY = 1 << 3; 80 81 /** 82 * The apk assets is owned by the application running in this process and incremental crash 83 * protections for this APK must be disabled. 84 */ 85 public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4; 86 87 /** 88 * The apk assets only contain the overlayable declarations information. 89 */ 90 public static final int PROPERTY_ONLY_OVERLAYABLES = 1 << 5; 91 92 /** Flags that change the behavior of loaded apk assets. */ 93 @IntDef(prefix = { "PROPERTY_" }, value = { 94 PROPERTY_SYSTEM, 95 PROPERTY_DYNAMIC, 96 PROPERTY_LOADER, 97 PROPERTY_OVERLAY, 98 }) 99 @Retention(RetentionPolicy.SOURCE) 100 public @interface PropertyFlags {} 101 102 /** The path used to load the apk assets represents an APK file. */ 103 private static final int FORMAT_APK = 0; 104 105 /** The path used to load the apk assets represents an idmap file. */ 106 private static final int FORMAT_IDMAP = 1; 107 108 /** The path used to load the apk assets represents an resources.arsc file. */ 109 private static final int FORMAT_ARSC = 2; 110 111 /** the path used to load the apk assets represents a directory. */ 112 private static final int FORMAT_DIR = 3; 113 114 // Format types that change how the apk assets are loaded. 115 @IntDef(prefix = { "FORMAT_" }, value = { 116 FORMAT_APK, 117 FORMAT_IDMAP, 118 FORMAT_ARSC, 119 FORMAT_DIR 120 }) 121 @Retention(RetentionPolicy.SOURCE) 122 public @interface FormatType {} 123 124 @GuardedBy("this") 125 private long mNativePtr; // final, except cleared in finalizer. 126 127 @Nullable 128 @GuardedBy("this") 129 private StringBlock mStringBlock; // null or closed if mNativePtr = 0. 130 131 @PropertyFlags 132 private final int mFlags; 133 134 private final boolean mIsOverlay; 135 136 @Nullable 137 private final AssetsProvider mAssets; 138 139 @NonNull 140 private String mName; 141 142 private static final int UPTODATE_FALSE = 0; 143 private static final int UPTODATE_TRUE = 1; 144 private static final int UPTODATE_ALWAYS_TRUE = 2; 145 146 // Start with the only value that may change later and would force a native call to 147 // double check it. 148 private int mPreviousUpToDateResult = UPTODATE_TRUE; 149 150 /** 151 * Creates a new ApkAssets instance from the given path on disk. 152 * 153 * @param path The path to an APK on disk. 154 * @return a new instance of ApkAssets. 155 * @throws IOException if a disk I/O error or parsing error occurred. 156 */ loadFromPath(@onNull String path)157 public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { 158 return loadFromPath(path, 0 /* flags */); 159 } 160 161 /** 162 * Creates a new ApkAssets instance from the given path on disk. 163 * 164 * @param path The path to an APK on disk. 165 * @param flags flags that change the behavior of loaded apk assets 166 * @return a new instance of ApkAssets. 167 * @throws IOException if a disk I/O error or parsing error occurred. 168 */ loadFromPath(@onNull String path, @PropertyFlags int flags)169 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags) 170 throws IOException { 171 return new ApkAssets(FORMAT_APK, path, flags, null /* assets */); 172 } 173 174 /** 175 * Creates a new ApkAssets instance from the given path on disk. 176 * 177 * @param path The path to an APK on disk. 178 * @param flags flags that change the behavior of loaded apk assets 179 * @param assets The assets provider that overrides the loading of file-based resources 180 * @return a new instance of ApkAssets. 181 * @throws IOException if a disk I/O error or parsing error occurred. 182 */ loadFromPath(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)183 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags, 184 @Nullable AssetsProvider assets) throws IOException { 185 return new ApkAssets(FORMAT_APK, path, flags, assets); 186 } 187 188 /** 189 * Creates a new ApkAssets instance from the given file descriptor. 190 * 191 * Performs a dup of the underlying fd, so you must take care of still closing 192 * the FileDescriptor yourself (and can do that whenever you want). 193 * 194 * @param fd The FileDescriptor of an open, readable APK. 195 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 196 * @param flags flags that change the behavior of loaded apk assets 197 * @param assets The assets provider that overrides the loading of file-based resources 198 * @return a new instance of ApkAssets. 199 * @throws IOException if a disk I/O error or parsing error occurred. 200 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)201 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 202 @NonNull String friendlyName, @PropertyFlags int flags, 203 @Nullable AssetsProvider assets) throws IOException { 204 return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets); 205 } 206 207 /** 208 * Creates a new ApkAssets instance from the given file descriptor. 209 * 210 * Performs a dup of the underlying fd, so you must take care of still closing 211 * the FileDescriptor yourself (and can do that whenever you want). 212 * 213 * @param fd The FileDescriptor of an open, readable APK. 214 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 215 * @param offset The location within the file that the apk starts. This must be 0 if length is 216 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 217 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 218 * if it extends to the end of the file. 219 * @param flags flags that change the behavior of loaded apk assets 220 * @param assets The assets provider that overrides the loading of file-based resources 221 * @return a new instance of ApkAssets. 222 * @throws IOException if a disk I/O error or parsing error occurred. 223 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)224 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 225 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 226 @Nullable AssetsProvider assets) 227 throws IOException { 228 return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); 229 } 230 231 /** 232 * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path 233 * is encoded within the IDMAP. 234 * 235 * @param idmapPath Path to the IDMAP of an overlay APK. 236 * @param flags flags that change the behavior of loaded apk assets 237 * @return a new instance of ApkAssets. 238 * @throws IOException if a disk I/O error or parsing error occurred. 239 */ loadOverlayFromPath(@onNull String idmapPath, @PropertyFlags int flags)240 public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, 241 @PropertyFlags int flags) throws IOException { 242 return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */); 243 } 244 245 /** 246 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 247 * for use with a {@link ResourcesProvider}. 248 * 249 * Performs a dup of the underlying fd, so you must take care of still closing 250 * the FileDescriptor yourself (and can do that whenever you want). 251 * 252 * @param fd The FileDescriptor of an open, readable resources.arsc. 253 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 254 * @param flags flags that change the behavior of loaded apk assets 255 * @param assets The assets provider that overrides the loading of file-based resources 256 * @return a new instance of ApkAssets. 257 * @throws IOException if a disk I/O error or parsing error occurred. 258 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)259 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 260 @NonNull String friendlyName, @PropertyFlags int flags, 261 @Nullable AssetsProvider assets) throws IOException { 262 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets); 263 } 264 265 /** 266 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 267 * for use with a {@link ResourcesProvider}. 268 * 269 * Performs a dup of the underlying fd, so you must take care of still closing 270 * the FileDescriptor yourself (and can do that whenever you want). 271 * 272 * @param fd The FileDescriptor of an open, readable resources.arsc. 273 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 274 * @param offset The location within the file that the table starts. This must be 0 if length is 275 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 276 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 277 * if it extends to the end of the file. 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 * @return a new instance of ApkAssets. 281 * @throws IOException if a disk I/O error or parsing error occurred. 282 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)283 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 284 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 285 @Nullable AssetsProvider assets) throws IOException { 286 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets); 287 } 288 289 /** 290 * Creates a new ApkAssets instance from the given directory path. The directory should have the 291 * file structure of an APK. 292 * 293 * @param path The path to a directory on disk. 294 * @param flags flags that change the behavior of loaded apk assets 295 * @param assets The assets provider that overrides the loading of file-based resources 296 * @return a new instance of ApkAssets. 297 * @throws IOException if a disk I/O error or parsing error occurred. 298 */ loadFromDir(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)299 public static @NonNull ApkAssets loadFromDir(@NonNull String path, 300 @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { 301 return new ApkAssets(FORMAT_DIR, path, flags, assets); 302 } 303 304 /** 305 * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence 306 * is required for a lot of APIs, and it's easier to have a non-null reference rather than 307 * tracking a separate identifier. 308 * 309 * @param flags flags that change the behavior of loaded apk assets 310 * @param assets The assets provider that overrides the loading of file-based resources 311 */ 312 @NonNull loadEmptyForLoader(@ropertyFlags int flags, @Nullable AssetsProvider assets)313 public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, 314 @Nullable AssetsProvider assets) { 315 return new ApkAssets(flags, assets); 316 } 317 ApkAssets(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)318 private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, 319 @Nullable AssetsProvider assets) throws IOException { 320 this(format, flags, assets, path); 321 Objects.requireNonNull(path, "path"); 322 mNativePtr = nativeLoad(format, path, flags, assets); 323 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 324 } 325 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)326 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 327 @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) 328 throws IOException { 329 this(format, flags, assets, friendlyName); 330 Objects.requireNonNull(fd, "fd"); 331 Objects.requireNonNull(friendlyName, "friendlyName"); 332 mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); 333 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 334 } 335 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)336 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 337 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 338 @Nullable AssetsProvider assets) throws IOException { 339 this(format, flags, assets, friendlyName); 340 Objects.requireNonNull(fd, "fd"); 341 Objects.requireNonNull(friendlyName, "friendlyName"); 342 mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); 343 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 344 } 345 ApkAssets(@ropertyFlags int flags, @Nullable AssetsProvider assets)346 private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { 347 this(FORMAT_APK, flags, assets, "empty"); 348 mNativePtr = nativeLoadEmpty(flags, assets); 349 mStringBlock = null; 350 } 351 ApkAssets(@ormatType int format, @PropertyFlags int flags, @Nullable AssetsProvider assets, @NonNull String name)352 private ApkAssets(@FormatType int format, @PropertyFlags int flags, 353 @Nullable AssetsProvider assets, @NonNull String name) { 354 mFlags = flags; 355 mAssets = assets; 356 mIsOverlay = format == FORMAT_IDMAP; 357 if (DEBUG) mName = name; 358 } 359 360 @UnsupportedAppUsage getAssetPath()361 public @NonNull String getAssetPath() { 362 synchronized (this) { 363 return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr)); 364 } 365 } 366 367 /** @hide */ getDebugName()368 public @NonNull String getDebugName() { 369 synchronized (this) { 370 return mNativePtr == 0 ? "<destroyed>" : nativeGetDebugName(mNativePtr); 371 } 372 } 373 374 @Nullable getStringFromPool(int idx)375 CharSequence getStringFromPool(int idx) { 376 if (mStringBlock == null) { 377 return null; 378 } 379 380 synchronized (this) { 381 return mStringBlock.getSequence(idx); 382 } 383 } 384 385 /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */ isForLoader()386 public boolean isForLoader() { 387 return (mFlags & PROPERTY_LOADER) != 0; 388 } 389 390 /** 391 * Returns the assets provider that overrides the loading of assets present in this apk assets. 392 */ 393 @Nullable getAssetsProvider()394 public AssetsProvider getAssetsProvider() { 395 return mAssets; 396 } 397 398 /** 399 * Retrieve a parser for a compiled XML file. This is associated with a single APK and 400 * <em>NOT</em> a full AssetManager. This means that shared-library references will not be 401 * dynamically assigned runtime package IDs. 402 * 403 * @param fileName The path to the file within the APK. 404 * @return An XmlResourceParser. 405 * @throws IOException if the file was not found or an error occurred retrieving it. 406 */ openXml(@onNull String fileName)407 public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { 408 Objects.requireNonNull(fileName, "fileName"); 409 synchronized (this) { 410 long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); 411 try (XmlBlock block = new XmlBlock(null, nativeXmlPtr, true)) { 412 XmlResourceParser parser = block.newParser(); 413 // If nativeOpenXml doesn't throw, it will always return a valid native pointer, 414 // which makes newParser always return non-null. But let's be careful. 415 if (parser == null) { 416 throw new AssertionError("block.newParser() returned a null parser"); 417 } 418 return parser; 419 } 420 } 421 } 422 423 /** @hide */ 424 @Nullable getOverlayableInfo(String overlayableName)425 public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException { 426 synchronized (this) { 427 return nativeGetOverlayableInfo(mNativePtr, overlayableName); 428 } 429 } 430 431 /** @hide */ definesOverlayable()432 public boolean definesOverlayable() throws IOException { 433 synchronized (this) { 434 return nativeDefinesOverlayable(mNativePtr); 435 } 436 } 437 intervalMs(long beginNs, long endNs)438 private static double intervalMs(long beginNs, long endNs) { 439 return (endNs - beginNs) / 1000000.0; 440 } 441 442 /** 443 * Returns false if the underlying APK was changed since this ApkAssets was loaded. 444 */ isUpToDate()445 public boolean isUpToDate() { 446 // This function is performance-critical - it's called multiple times on every Resources 447 // object creation, and on few other cache accesses - so it's important to avoid the native 448 // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE 449 // and FALSE). 450 if (mPreviousUpToDateResult != UPTODATE_TRUE) { 451 return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE; 452 } 453 final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs; 454 if (DEBUG) beforeTs = System.nanoTime(); 455 final int res; 456 synchronized (this) { 457 if (DEBUG) afterLockTs = System.nanoTime(); 458 res = nativeIsUpToDate(mNativePtr); 459 if (DEBUG) afterNativeTs = System.nanoTime(); 460 } 461 if (DEBUG) { 462 afterUnlockTs = System.nanoTime(); 463 if (afterUnlockTs - beforeTs >= 10L * 1000000) { 464 Log.d("ApkAssets", "isUpToDate(" + mName + ") took " 465 + intervalMs(beforeTs, afterUnlockTs) 466 + " ms: " + intervalMs(beforeTs, afterLockTs) 467 + " / " + intervalMs(afterLockTs, afterNativeTs) 468 + " / " + intervalMs(afterNativeTs, afterUnlockTs)); 469 } 470 } 471 mPreviousUpToDateResult = res; 472 return res != UPTODATE_FALSE; 473 } 474 isSystem()475 public boolean isSystem() { 476 return (mFlags & PROPERTY_SYSTEM) != 0; 477 } 478 isSharedLib()479 public boolean isSharedLib() { 480 return (mFlags & PROPERTY_DYNAMIC) != 0; 481 } 482 isOverlay()483 public boolean isOverlay() { 484 return mIsOverlay; 485 } 486 487 @Override toString()488 public String toString() { 489 return "ApkAssets{path=" + getDebugName() + "}"; 490 } 491 492 @Override finalize()493 protected void finalize() throws Throwable { 494 close(); 495 } 496 497 /** 498 * Closes this class and the contained {@link #mStringBlock}. 499 */ close()500 public void close() { 501 synchronized (this) { 502 if (mNativePtr != 0) { 503 if (mStringBlock != null) { 504 mStringBlock.close(); 505 } 506 nativeDestroy(mNativePtr); 507 mNativePtr = 0; 508 } 509 } 510 } 511 dump(PrintWriter pw, String prefix)512 void dump(PrintWriter pw, String prefix) { 513 pw.println(prefix + "class=" + getClass()); 514 pw.println(prefix + "debugName=" + getDebugName()); 515 pw.println(prefix + "assetPath=" + getAssetPath()); 516 } 517 nativeLoad(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset)518 private static native long nativeLoad(@FormatType int format, @NonNull String path, 519 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeLoadEmpty(@ropertyFlags int flags, @Nullable AssetsProvider asset)520 private static native long nativeLoadEmpty(@PropertyFlags int flags, 521 @Nullable AssetsProvider asset); nativeLoadFd(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider asset)522 private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, 523 @NonNull String friendlyName, @PropertyFlags int flags, 524 @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)525 private static native long nativeLoadFdOffsets(@FormatType int format, 526 @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, 527 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeDestroy(long ptr)528 private static native void nativeDestroy(long ptr); nativeGetAssetPath(long ptr)529 private static native @NonNull String nativeGetAssetPath(long ptr); nativeGetDebugName(long ptr)530 private static native @NonNull String nativeGetDebugName(long ptr); nativeGetStringBlock(long ptr)531 private static native long nativeGetStringBlock(long ptr); nativeIsUpToDate(long ptr)532 @CriticalNative private static native int nativeIsUpToDate(long ptr); nativeOpenXml(long ptr, @NonNull String fileName)533 private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; nativeGetOverlayableInfo(long ptr, String overlayableName)534 private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, 535 String overlayableName) throws IOException; nativeDefinesOverlayable(long ptr)536 private static native boolean nativeDefinesOverlayable(long ptr) throws IOException; 537 } 538