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

Commit e70b57a6 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

Make idiomatic use of ApkAssets and AssetManager

Move away from using deprecated addAssetPath methods
and cache the instances of ApkAssets created.

Test: CTS passes
Change-Id: I257c72261a97e4aa802abb46dc1f44d80e1d42ad
parent dcb3c655
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -6303,6 +6303,8 @@ public class Activity extends ContextThemeWrapper
        } else {
            writer.print(prefix); writer.println("No AutofillManager");
        }

        ResourcesManager.getInstance().dump(prefix, writer);
    }

    /**
+168 −30
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.CompatResources;
import android.content.res.CompatibilityInfo;
@@ -34,6 +35,7 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
@@ -41,9 +43,13 @@ import android.view.DisplayAdjustments;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -59,12 +65,7 @@ public class ResourcesManager {
     * Predicate that returns true if a WeakReference is gc'ed.
     */
    private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
            new Predicate<WeakReference<Resources>>() {
                @Override
                public boolean test(WeakReference<Resources> weakRef) {
                    return weakRef == null || weakRef.get() == null;
                }
            };
            weakRef -> weakRef == null || weakRef.get() == null;

    /**
     * The global compatibility settings.
@@ -89,6 +90,48 @@ public class ResourcesManager {
     */
    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();

    private static class ApkKey {
        public final String path;
        public final boolean sharedLib;
        public final boolean overlay;

        ApkKey(String path, boolean sharedLib, boolean overlay) {
            this.path = path;
            this.sharedLib = sharedLib;
            this.overlay = overlay;
        }

        @Override
        public int hashCode() {
            int result = 1;
            result = 31 * result + this.path.hashCode();
            result = 31 * result + Boolean.hashCode(this.sharedLib);
            result = 31 * result + Boolean.hashCode(this.overlay);
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof ApkKey)) {
                return false;
            }
            ApkKey other = (ApkKey) obj;
            return this.path.equals(other.path) && this.sharedLib == other.sharedLib
                    && this.overlay == other.overlay;
        }
    }

    /**
     * The ApkAssets we are caching and intend to hold strong references to.
     */
    private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15);

    /**
     * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
     * in our LRU cache. Bonus resources :)
     */
    private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();

    /**
     * Resources and base configuration override associated with an Activity.
     */
@@ -260,6 +303,41 @@ public class ResourcesManager {
        }
    }

    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
            throws IOException {
        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
        ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
        if (apkAssets != null) {
            return apkAssets;
        }

        // Optimistically check if this ApkAssets exists somewhere else.
        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
        if (apkAssetsRef != null) {
            apkAssets = apkAssetsRef.get();
            if (apkAssets != null) {
                mLoadedApkAssets.put(newKey, apkAssets);
                return apkAssets;
            } else {
                // Clean up the reference.
                mCachedApkAssets.remove(newKey);
            }
        }

        // We must load this from disk.
        if (overlay) {
            final String idmapPath = "/data/resource-cache/"
                    + path.substring(1).replace('/', '@')
                    + "@idmap";
            apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
        } else {
            apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
        }
        mLoadedApkAssets.put(newKey, apkAssets);
        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
        return apkAssets;
    }

    /**
     * Creates an AssetManager from the paths within the ResourcesKey.
     *
@@ -270,13 +348,15 @@ public class ResourcesManager {
    */
    @VisibleForTesting
    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        AssetManager assets = new AssetManager();
        final ArrayList<ApkAssets> apkAssets = new ArrayList<>();

        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (key.mResDir != null) {
            if (assets.addAssetPath(key.mResDir) == 0) {
            try {
                apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/));
            } catch (IOException e) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
@@ -284,7 +364,10 @@ public class ResourcesManager {

        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                if (assets.addAssetPath(splitResDir) == 0) {
                try {
                    apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/,
                            false /*overlay*/));
                } catch (IOException e) {
                    Log.e(TAG, "failed to add split asset path " + splitResDir);
                    return null;
                }
@@ -293,7 +376,13 @@ public class ResourcesManager {

        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                assets.addOverlayPath(idmapPath);
                try {
                    apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/));
                } catch (IOException e) {
                    Log.w(TAG, "failed to add overlay path " + idmapPath);

                    // continue.
                }
            }
        }

@@ -302,16 +391,77 @@ public class ResourcesManager {
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                    try {
                        apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/));
                    } catch (IOException e) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");

                        // continue.
                    }
                }
            }
        }

        AssetManager assets = new AssetManager();
        assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]),
                false /*invalidateCaches*/);
        return assets;
    }

    private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
        int count = 0;
        for (WeakReference<T> ref : collection) {
            final T value = ref != null ? ref.get() : null;
            if (value != null) {
                count++;
            }
        }
        return count;
    }

    /**
     * @hide
     */
    public void dump(String prefix, PrintWriter printWriter) {
        synchronized (this) {
            IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
            for (int i = 0; i < prefix.length() / 2; i++) {
                pw.increaseIndent();
            }

            pw.println("ResourcesManager:");
            pw.increaseIndent();
            pw.print("cached apks: total=");
            pw.print(mLoadedApkAssets.size());
            pw.print(" created=");
            pw.print(mLoadedApkAssets.createCount());
            pw.print(" evicted=");
            pw.print(mLoadedApkAssets.evictionCount());
            pw.print(" hit=");
            pw.print(mLoadedApkAssets.hitCount());
            pw.print(" miss=");
            pw.print(mLoadedApkAssets.missCount());
            pw.print(" max=");
            pw.print(mLoadedApkAssets.maxSize());
            pw.println();

            pw.print("total apks: ");
            pw.println(countLiveReferences(mCachedApkAssets.values()));

            pw.print("resources: ");

            int references = countLiveReferences(mResourceReferences);
            for (ActivityResources activityResources : mActivityResourceReferences.values()) {
                references += countLiveReferences(activityResources.activityResources);
            }
            pw.println(references);

            pw.print("resource impls: ");
            pw.println(countLiveReferences(mResourceImpls.values()));
        }
    }

    private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
        Configuration config;
        final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
@@ -630,7 +780,6 @@ public class ResourcesManager {

                // We will create the ResourcesImpl object outside of holding this lock.
            }
        }

            // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
            ResourcesImpl resourcesImpl = createResourcesImpl(key);
@@ -638,19 +787,8 @@ public class ResourcesManager {
                return null;
            }

        synchronized (this) {
            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
            if (existingResourcesImpl != null) {
                if (DEBUG) {
                    Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
                            + " new impl=" + resourcesImpl);
                }
                resourcesImpl.getAssets().close();
                resourcesImpl = existingResourcesImpl;
            } else {
            // Add this ResourcesImpl to the cache.
            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
            }

            final Resources resources;
            if (activityToken != null) {
+16 −29
Original line number Diff line number Diff line
@@ -1292,24 +1292,6 @@ public class PackageParser {
        }
    }

    private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
            throws PackageParserException {
        if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "Invalid package file: " + apkPath);
        }

        // The AssetManager guarantees uniqueness for asset paths, so if this asset path
        // already exists in the AssetManager, addAssetPath will only return the cookie
        // assigned to it.
        int cookie = assets.addAssetPath(apkPath);
        if (cookie == 0) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Failed adding asset path: " + apkPath);
        }
        return cookie;
    }

    private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();
@@ -1325,13 +1307,15 @@ public class PackageParser {

        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);

        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

        Resources res = null;
        XmlResourceParser parser = null;
        try {
            res = new Resources(assets, mMetrics, null);
            final int cookie = assets.findCookieForPath(apkPath);
            if (cookie == 0) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Failed adding asset path: " + apkPath);
            }
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
            final Resources res = new Resources(assets, mMetrics, null);

            final String[] outError = new String[1];
            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
@@ -1366,15 +1350,18 @@ public class PackageParser {

        if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);

        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

        final Resources res;
        XmlResourceParser parser = null;
        try {
            res = new Resources(assets, mMetrics, null);
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);
            // This must always succeed, as the path has been added to the AssetManager before.
            final int cookie = assets.findCookieForPath(apkPath);
            if (cookie == 0) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Failed adding asset path: " + apkPath);
            }

            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
            res = new Resources(assets, mMetrics, null);

            final String[] outError = new String[1];
            pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
@@ -1576,9 +1563,9 @@ public class PackageParser {
            int flags) throws PackageParserException {
        final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();

        ApkAssets apkAssets = null;
        XmlResourceParser parser = null;
        try {
            final ApkAssets apkAssets;
            try {
                apkAssets = fd != null
                        ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
@@ -1614,7 +1601,7 @@ public class PackageParser {
                    "Failed to parse " + apkPath, e);
        } finally {
            IoUtils.closeQuietly(parser);
            IoUtils.closeQuietly(apkAssets);
            // TODO(b/72056911): Implement and call close() on ApkAssets.
        }
    }

+39 −36
Original line number Diff line number Diff line
@@ -15,10 +15,13 @@
 */
package android.content.pm.split;

import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;

import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;

@@ -26,6 +29,8 @@ import com.android.internal.util.ArrayUtils;

import libcore.io.IoUtils;

import java.io.IOException;

/**
 * Loads the base and split APKs into a single AssetManager.
 * @hide
@@ -33,68 +38,66 @@ import libcore.io.IoUtils;
public class DefaultSplitAssetLoader implements SplitAssetLoader {
    private final String mBaseCodePath;
    private final String[] mSplitCodePaths;
    private final int mFlags;

    private final @ParseFlags int mFlags;
    private AssetManager mCachedAssetManager;

    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
        mBaseCodePath = pkg.baseCodePath;
        mSplitCodePaths = pkg.splitCodePaths;
        mFlags = flags;
    }

    private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
            throws PackageParser.PackageParserException {
        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
            throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "Invalid package file: " + apkPath);
    private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
            throws PackageParserException {
        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "Invalid package file: " + path);
        }

        if (assets.addAssetPath(apkPath) == 0) {
            throw new PackageParser.PackageParserException(
                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Failed adding asset path: " + apkPath);
        try {
            return ApkAssets.loadFromPath(path);
        } catch (IOException e) {
            throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
                    "Failed to load APK at path " + path, e);
        }
    }

    @Override
    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
    public AssetManager getBaseAssetManager() throws PackageParserException {
        if (mCachedAssetManager != null) {
            return mCachedAssetManager;
        }

        AssetManager assets = new AssetManager();
        try {
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);
            loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
        ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
                ? mSplitCodePaths.length : 0) + 1];

        // Load the base.
        int splitIdx = 0;
        apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);

        // Load any splits.
        if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
            for (String apkPath : mSplitCodePaths) {
                    loadApkIntoAssetManager(assets, apkPath, mFlags);
                apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
            }
        }

        AssetManager assets = new AssetManager();
        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                Build.VERSION.RESOURCES_SDK_INT);
        assets.setApkAssets(apkAssets, false /*invalidateCaches*/);

        mCachedAssetManager = assets;
            assets = null;
        return mCachedAssetManager;
        } finally {
            if (assets != null) {
                IoUtils.closeQuietly(assets);
            }
        }
    }

    @Override
    public AssetManager getSplitAssetManager(int splitIdx)
            throws PackageParser.PackageParserException {
    public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
        return getBaseAssetManager();
    }

    @Override
    public void close() throws Exception {
        if (mCachedAssetManager != null) {
        IoUtils.closeQuietly(mCachedAssetManager);
    }
}
}
+46 −42
Original line number Diff line number Diff line
@@ -15,17 +15,21 @@
 */
package android.content.pm.split;

import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;

import android.annotation.NonNull;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.SparseArray;

import libcore.io.IoUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

@@ -34,17 +38,15 @@ import java.util.Collections;
 * is to be used when an application opts-in to isolated split loading.
 * @hide
 */
public class SplitAssetDependencyLoader
        extends SplitDependencyLoader<PackageParser.PackageParserException>
public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
        implements SplitAssetLoader {
    private final String[] mSplitPaths;
    private final int mFlags;

    private String[][] mCachedPaths;
    private AssetManager[] mCachedAssetManagers;
    private final @ParseFlags int mFlags;
    private final ApkAssets[][] mCachedSplitApks;
    private final AssetManager[] mCachedAssetManagers;

    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
            SparseArray<int[]> dependencies, int flags) {
            SparseArray<int[]> dependencies, @ParseFlags int flags) {
        super(dependencies);

        // The base is inserted into index 0, so we need to shift all the splits by 1.
@@ -53,7 +55,7 @@ public class SplitAssetDependencyLoader
        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);

        mFlags = flags;
        mCachedPaths = new String[mSplitPaths.length][];
        mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
        mCachedAssetManagers = new AssetManager[mSplitPaths.length];
    }

@@ -62,58 +64,60 @@ public class SplitAssetDependencyLoader
        return mCachedAssetManagers[splitIdx] != null;
    }

    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
            throws PackageParser.PackageParserException {
        final AssetManager assets = new AssetManager();
        try {
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);

            for (String assetPath : assetPaths) {
                if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
                        !PackageParser.isApkPath(assetPath)) {
                    throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                            "Invalid package file: " + assetPath);
    private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
            throws PackageParserException {
        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "Invalid package file: " + path);
        }

                if (assets.addAssetPath(assetPath) == 0) {
                    throw new PackageParser.PackageParserException(
                            INSTALL_PARSE_FAILED_BAD_MANIFEST,
                            "Failed adding asset path: " + assetPath);
        try {
            return ApkAssets.loadFromPath(path);
        } catch (IOException e) {
            throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
                    "Failed to load APK at path " + path, e);
        }
    }

    private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
        final AssetManager assets = new AssetManager();
        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                Build.VERSION.RESOURCES_SDK_INT);
        assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
        return assets;
        } catch (Throwable e) {
            IoUtils.closeQuietly(assets);
            throw e;
        }
    }

    @Override
    protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
            int parentSplitIdx) throws PackageParser.PackageParserException {
        final ArrayList<String> assetPaths = new ArrayList<>();
            int parentSplitIdx) throws PackageParserException {
        final ArrayList<ApkAssets> assets = new ArrayList<>();

        // Include parent ApkAssets.
        if (parentSplitIdx >= 0) {
            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
            Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
        }

        assetPaths.add(mSplitPaths[splitIdx]);
        // Include this ApkAssets.
        assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));

        // Load and include all config splits for this feature.
        for (int configSplitIdx : configSplitIndices) {
            assetPaths.add(mSplitPaths[configSplitIdx]);
            assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
        }
        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
                mFlags);

        // Cache the results.
        mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
        mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
    }

    @Override
    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
    public AssetManager getBaseAssetManager() throws PackageParserException {
        loadDependenciesForSplit(0);
        return mCachedAssetManagers[0];
    }

    @Override
    public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
    public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
        // Since we insert the base at position 0, and PackageParser keeps splits separate from
        // the base, we need to adjust the index.
        loadDependenciesForSplit(idx + 1);
Loading