Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8e4f087c authored by Winson Chiu's avatar Winson Chiu Committed by Android (Google) Code Review
Browse files

Merge "Add ResourceLoader API with .apk and .arsc support"

parents 535bd634 9947f1e4
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -12402,6 +12402,8 @@ package android.content.res {
  public class Resources {
    ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
    method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
    method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
    method public final void finishPreloading();
    method public final void flushLayoutCache();
    method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimatorRes @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -12428,6 +12430,7 @@ package android.content.res {
    method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
    method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
    method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
    method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
    method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
    method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
    method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12455,6 +12458,8 @@ package android.content.res {
    method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
    method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
    method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
    method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
    method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
    method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
    field @AnyRes public static final int ID_NULL = 0; // 0x0
  }
@@ -12522,6 +12527,33 @@ package android.content.res {
}
package android.content.res.loader {
  public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
    ctor public DirectoryResourceLoader(@NonNull java.io.File);
    method @Nullable public java.io.File findFile(@NonNull String);
    method @NonNull public java.io.File getDirectory();
  }
  public interface ResourceLoader {
    method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
    method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
    method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
    method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
  }
  public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
    method public void close();
    method @NonNull public static android.content.res.loader.ResourcesProvider empty();
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
  }
}
package android.database {
  public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+2 −0
Original line number Diff line number Diff line
@@ -788,7 +788,9 @@ package android.content.res {

  public final class AssetManager implements java.lang.AutoCloseable {
    method @NonNull public String[] getApkPaths();
    method @Nullable public String getLastResourceResolution();
    method @Nullable public String getOverlayablesToString(String);
    method public void setResourceResolutionLoggingEnabled(boolean);
  }

  public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
+496 −197

File changed.

Preview size limit exceeded, changes collapsed.

+109 −13
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@
package android.content.res;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.res.loader.ResourcesProvider;
import android.text.TextUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -36,10 +39,14 @@ import java.io.IOException;
 */
public final class ApkAssets {
    @GuardedBy("this") private final long mNativePtr;

    @Nullable
    @GuardedBy("this") private final StringBlock mStringBlock;

    @GuardedBy("this") private boolean mOpen = true;

    private final boolean mForLoader;

    /**
     * Creates a new ApkAssets instance from the given path on disk.
     *
@@ -48,7 +55,8 @@ public final class ApkAssets {
     * @throws IOException if a disk I/O error or parsing error occurred.
     */
    public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
        return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
        return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
                false /*arscOnly*/, false /*forLoader*/);
    }

    /**
@@ -61,7 +69,8 @@ public final class ApkAssets {
     */
    public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
            throws IOException {
        return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
        return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
                false /*arscOnly*/, false /*forLoader*/);
    }

    /**
@@ -76,7 +85,8 @@ public final class ApkAssets {
     */
    public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
            boolean forceSharedLibrary) throws IOException {
        return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
        return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
                false /*arscOnly*/, false /*forLoader*/);
    }

    /**
@@ -96,7 +106,8 @@ public final class ApkAssets {
    public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
            @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
            throws IOException {
        return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
        return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
                false /*forLoader*/);
    }

    /**
@@ -110,21 +121,90 @@ public final class ApkAssets {
     */
    public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
            throws IOException {
        return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
        return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
                false /*arscOnly*/, false /*forLoader*/);
    }

    /**
     * Creates a new ApkAssets instance from the given path on disk for use with a
     * {@link ResourcesProvider}.
     *
     * @param path The path to an APK on disk.
     * @return a new instance of ApkAssets.
     * @throws IOException if a disk I/O error or parsing error occurred.
     */
    public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
            throws IOException {
        return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
                false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
    }

    /**
     * Creates a new ApkAssets instance from the given file descriptor for use with a
     * {@link ResourcesProvider}.
     *
     * Performs a dup of the underlying fd, so you must take care of still closing
     * the FileDescriptor yourself (and can do that whenever you want).
     *
     * @param fd The FileDescriptor of an open, readable APK.
     * @return a new instance of ApkAssets.
     * @throws IOException if a disk I/O error or parsing error occurred.
     */
    @NonNull
    public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
        return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
                false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
    }

    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
    /**
     * Creates a new ApkAssets instance from the given file descriptor representing an ARSC
     * for use with a {@link ResourcesProvider}.
     *
     * Performs a dup of the underlying fd, so you must take care of still closing
     * the FileDescriptor yourself (and can do that whenever you want).
     *
     * @param fd The FileDescriptor of an open, readable .arsc.
     * @return a new instance of ApkAssets.
     * @throws IOException if a disk I/O error or parsing error occurred.
     */
    public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd)
            throws IOException {
        return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
                false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
    }

    /**
     * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
     * is required for a lot of APIs, and it's easier to have a non-null reference rather than
     * tracking a separate identifier.
     */
    @NonNull
    public static ApkAssets loadEmptyForLoader() {
        return new ApkAssets(true);
    }

    private ApkAssets(boolean forLoader) {
        mForLoader = forLoader;
        mNativePtr = nativeLoadEmpty(forLoader);
        mStringBlock = null;
    }

    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
            boolean arscOnly, boolean forLoader) throws IOException {
        mForLoader = forLoader;
        Preconditions.checkNotNull(path, "path");
        mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
        mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
                : nativeLoad(path, system, forceSharedLib, overlay, forLoader);
        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
    }

    private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
            boolean forceSharedLib) throws IOException {
            boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
        mForLoader = forLoader;
        Preconditions.checkNotNull(fd, "fd");
        Preconditions.checkNotNull(friendlyName, "friendlyName");
        mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
        mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
                : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
    }

@@ -136,11 +216,19 @@ public final class ApkAssets {
    }

    CharSequence getStringFromPool(int idx) {
        if (mStringBlock == null) {
            return null;
        }

        synchronized (this) {
            return mStringBlock.get(idx);
        }
    }

    public boolean isForLoader() {
        return mForLoader;
    }

    /**
     * Retrieve a parser for a compiled XML file. This is associated with a single APK and
     * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
@@ -192,18 +280,26 @@ public final class ApkAssets {
        synchronized (this) {
            if (mOpen) {
                mOpen = false;
                if (mStringBlock != null) {
                    mStringBlock.close();
                }
                nativeDestroy(mNativePtr);
            }
        }
    }

    private static native long nativeLoad(
            @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
    private static native long nativeLoad(@NonNull String path, boolean system,
            boolean forceSharedLib, boolean overlay, boolean forLoader)
            throws IOException;
    private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
            @NonNull String friendlyName, boolean system, boolean forceSharedLib)
            @NonNull String friendlyName, boolean system, boolean forceSharedLib,
            boolean forLoader)
            throws IOException;
    private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
            throws IOException;
    private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
            @NonNull String friendlyName, boolean forLoader) throws IOException;
    private static native long nativeLoadEmpty(boolean forLoader);
    private static native void nativeDestroy(long ptr);
    private static native @NonNull String nativeGetAssetPath(long ptr);
    private static native long nativeGetStringBlock(long ptr);
+139 −6
Original line number Diff line number Diff line
@@ -27,9 +27,13 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
import android.content.res.loader.ResourceLoader;
import android.content.res.loader.ResourceLoaderManager;
import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;

@@ -39,15 +43,19 @@ import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;

import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileLock;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
@@ -110,6 +118,13 @@ public final class AssetManager implements AutoCloseable {
    @GuardedBy("this") private int mNumRefs = 1;
    @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;

    private ResourceLoaderManager mResourceLoaderManager;

    /** @hide */
    public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
        mResourceLoaderManager = resourceLoaderManager;
    }

    /**
     * A Builder class that helps create an AssetManager with only a single invocation of
     * {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
@@ -507,7 +522,7 @@ public final class AssetManager implements AutoCloseable {
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
                outValue.string = getPooledStringForCookie(cookie, outValue.data);
            }
            return true;
        }
@@ -554,7 +569,7 @@ public final class AssetManager implements AutoCloseable {
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
                return getPooledStringForCookie(cookie, outValue.data);
            }
            return outValue.coerceToString();
        }
@@ -632,7 +647,7 @@ public final class AssetManager implements AutoCloseable {
                int cookie = rawInfoArray[i];
                int index = rawInfoArray[i + 1];
                retArray[j] = (index >= 0 && cookie > 0)
                        ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
                        ? getPooledStringForCookie(cookie, index) : null;
            }
            return retArray;
        }
@@ -688,7 +703,7 @@ public final class AssetManager implements AutoCloseable {
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
                outValue.string = getPooledStringForCookie(cookie, outValue.data);
            }
            return true;
        }
@@ -753,6 +768,7 @@ public final class AssetManager implements AutoCloseable {
     *
     * @hide
     */
    @TestApi
    public void setResourceResolutionLoggingEnabled(boolean enabled) {
        synchronized (this) {
            ensureValidLocked();
@@ -768,6 +784,7 @@ public final class AssetManager implements AutoCloseable {
     *
     * @hide
     */
    @TestApi
    public @Nullable String getLastResourceResolution() {
        synchronized (this) {
            ensureValidLocked();
@@ -814,6 +831,13 @@ public final class AssetManager implements AutoCloseable {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();

            String path = Paths.get("assets", fileName).toString();
            InputStream inputStream = searchLoaders(0, path, accessMode);
            if (inputStream != null) {
                return inputStream;
            }

            final long asset = nativeOpenAsset(mObject, fileName, accessMode);
            if (asset == 0) {
                throw new FileNotFoundException("Asset file: " + fileName);
@@ -838,6 +862,13 @@ public final class AssetManager implements AutoCloseable {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();

            String path = Paths.get("assets", fileName).toString();
            AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
            if (fileDescriptor != null) {
                return fileDescriptor;
            }

            final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
            if (pfd == null) {
                throw new FileNotFoundException("Asset file: " + fileName);
@@ -931,6 +962,12 @@ public final class AssetManager implements AutoCloseable {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();

            InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
            if (inputStream != null) {
                return inputStream;
            }

            final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
            if (asset == 0) {
                throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -970,6 +1007,12 @@ public final class AssetManager implements AutoCloseable {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();

            AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
            if (fileDescriptor != null) {
                return fileDescriptor;
            }

            final ParcelFileDescriptor pfd =
                    nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
            if (pfd == null) {
@@ -1031,7 +1074,16 @@ public final class AssetManager implements AutoCloseable {
        Preconditions.checkNotNull(fileName, "fileName");
        synchronized (this) {
            ensureOpenLocked();
            final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);

            final long xmlBlock;
            AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
            if (fileDescriptor != null) {
                xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
                        fileDescriptor.getFileDescriptor());
            } else {
                xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
            }

            if (xmlBlock == 0) {
                throw new FileNotFoundException("Asset XML file: " + fileName);
            }
@@ -1041,6 +1093,85 @@ public final class AssetManager implements AutoCloseable {
        }
    }

    private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
            throws IOException {
        if (mResourceLoaderManager == null) {
            return null;
        }

        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
                mResourceLoaderManager.getInternalList();

        // A cookie of 0 means no specific ApkAssets, so search everything
        if (cookie == 0) {
            for (int index = loaders.size() - 1; index >= 0; index--) {
                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
                try {
                    InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
                    if (inputStream != null) {
                        return inputStream;
                    }
                } catch (IOException ignored) {
                    // When searching, ignore read failures
                }
            }

            return null;
        }

        ApkAssets apkAssets = mApkAssets[cookie - 1];
        for (int index = loaders.size() - 1; index >= 0; index--) {
            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
            if (pair.second.getApkAssets() == apkAssets) {
                return pair.first.loadAsset(fileName, accessMode);
            }
        }

        return null;
    }

    private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
            throws IOException {
        if (mResourceLoaderManager == null) {
            return null;
        }

        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
                mResourceLoaderManager.getInternalList();

        // A cookie of 0 means no specific ApkAssets, so search everything
        if (cookie == 0) {
            for (int index = loaders.size() - 1; index >= 0; index--) {
                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
                try {
                    ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
                    if (fileDescriptor != null) {
                        return new AssetFileDescriptor(fileDescriptor, 0,
                                AssetFileDescriptor.UNKNOWN_LENGTH);
                    }
                } catch (IOException ignored) {
                    // When searching, ignore read failures
                }
            }

            return null;
        }

        ApkAssets apkAssets = mApkAssets[cookie - 1];
        for (int index = loaders.size() - 1; index >= 0; index--) {
            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
            if (pair.second.getApkAssets() == apkAssets) {
                ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
                if (fileDescriptor != null) {
                    return new AssetFileDescriptor(fileDescriptor, 0,
                            AssetFileDescriptor.UNKNOWN_LENGTH);
                }
                return null;
            }
        }
        return null;
    }

    void xmlBlockGone(int id) {
        synchronized (this) {
            decRefsLocked(id);
@@ -1296,7 +1427,7 @@ public final class AssetManager implements AutoCloseable {
     *
     * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
     * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
     * parsed using {@link java.util.Locale#forLanguageTag(String)}.
     * parsed using {@link Locale#forLanguageTag(String)}.
     *
     * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
     * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
@@ -1439,6 +1570,8 @@ public final class AssetManager implements AutoCloseable {
    private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
            @NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
    private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
    private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
            @NonNull FileDescriptor fileDescriptor);

    // Primitive resource native methods.
    private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
Loading