Revert "Make idiomatic use of ApkAssets and AssetManager"

This reverts commit c857766ca5aa37df0833c816922e6b3b85a742f9.
(cherry picked from commit 68d10d00a52be904766d69acda98bc934e768e7f)

Change-Id: Ib817de28884adceb4357f3d93d55d7421d155fae
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a6a1f43..0a5b848 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6303,8 +6303,6 @@
         } else {
             writer.print(prefix); writer.println("No AutofillManager");
         }
-
-        ResourcesManager.getInstance().dump(prefix, writer);
     }
 
     /**
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index b96e028..fb11272 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -21,7 +21,6 @@
 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;
@@ -35,7 +34,6 @@
 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;
@@ -43,13 +41,9 @@
 
 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;
@@ -65,7 +59,12 @@
      * Predicate that returns true if a WeakReference is gc'ed.
      */
     private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
-            weakRef -> weakRef == null || weakRef.get() == null;
+            new Predicate<WeakReference<Resources>>() {
+                @Override
+                public boolean test(WeakReference<Resources> weakRef) {
+                    return weakRef == null || weakRef.get() == null;
+                }
+            };
 
     /**
      * The global compatibility settings.
@@ -90,48 +89,6 @@
      */
     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.
      */
@@ -303,41 +260,6 @@
         }
     }
 
-    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.
      *
@@ -348,15 +270,13 @@
     */
     @VisibleForTesting
     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
-        final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+        AssetManager assets = new AssetManager();
 
         // 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) {
-            try {
-                apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/));
-            } catch (IOException e) {
+            if (assets.addAssetPath(key.mResDir) == 0) {
                 Log.e(TAG, "failed to add asset path " + key.mResDir);
                 return null;
             }
@@ -364,10 +284,7 @@
 
         if (key.mSplitResDirs != null) {
             for (final String splitResDir : key.mSplitResDirs) {
-                try {
-                    apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/,
-                            false /*overlay*/));
-                } catch (IOException e) {
+                if (assets.addAssetPath(splitResDir) == 0) {
                     Log.e(TAG, "failed to add split asset path " + splitResDir);
                     return null;
                 }
@@ -376,13 +293,7 @@
 
         if (key.mOverlayDirs != null) {
             for (final String idmapPath : key.mOverlayDirs) {
-                try {
-                    apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/));
-                } catch (IOException e) {
-                    Log.w(TAG, "failed to add overlay path " + idmapPath);
-
-                    // continue.
-                }
+                assets.addOverlayPath(idmapPath);
             }
         }
 
@@ -391,77 +302,16 @@
                 if (libDir.endsWith(".apk")) {
                     // Avoid opening files we know do not have resources,
                     // like code-only .jar files.
-                    try {
-                        apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/));
-                    } catch (IOException e) {
+                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                         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);
@@ -780,16 +630,28 @@
 
                 // 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);
-            if (resourcesImpl == null) {
-                return null;
+        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+        ResourcesImpl resourcesImpl = createResourcesImpl(key);
+        if (resourcesImpl == null) {
+            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));
             }
 
-            // Add this ResourcesImpl to the cache.
-            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
-
             final Resources resources;
             if (activityToken != null) {
                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 68aaaa3..8509274 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1292,6 +1292,24 @@
         }
     }
 
+    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();
@@ -1307,15 +1325,13 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
+        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
+
+        Resources res = null;
         XmlResourceParser parser = null;
         try {
-            final int cookie = assets.findCookieForPath(apkPath);
-            if (cookie == 0) {
-                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                        "Failed adding asset path: " + apkPath);
-            }
+            res = new Resources(assets, mMetrics, null);
             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);
@@ -1350,18 +1366,15 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
 
+        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
+
         final Resources res;
         XmlResourceParser parser = null;
         try {
-            // 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);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
             final String[] outError = new String[1];
             pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
@@ -1563,9 +1576,9 @@
             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)
@@ -1601,7 +1614,7 @@
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            // TODO(b/72056911): Implement and call close() on ApkAssets.
+            IoUtils.closeQuietly(apkAssets);
         }
     }
 
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 9e3a8f4..99eb470 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -15,13 +15,10 @@
  */
 package android.content.pm.split;
 
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 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;
 
@@ -29,8 +26,6 @@
 
 import libcore.io.IoUtils;
 
-import java.io.IOException;
-
 /**
  * Loads the base and split APKs into a single AssetManager.
  * @hide
@@ -38,66 +33,68 @@
 public class DefaultSplitAssetLoader implements SplitAssetLoader {
     private final String mBaseCodePath;
     private final String[] mSplitCodePaths;
-    private final @ParseFlags int mFlags;
+    private final int mFlags;
+
     private AssetManager mCachedAssetManager;
 
-    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
+    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
         mBaseCodePath = pkg.baseCodePath;
         mSplitCodePaths = pkg.splitCodePaths;
         mFlags = flags;
     }
 
-    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);
+    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);
         }
 
-        try {
-            return ApkAssets.loadFromPath(path);
-        } catch (IOException e) {
-            throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
-                    "Failed to load APK at path " + path, e);
+        if (assets.addAssetPath(apkPath) == 0) {
+            throw new PackageParser.PackageParserException(
+                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                    "Failed adding asset path: " + apkPath);
         }
     }
 
     @Override
-    public AssetManager getBaseAssetManager() throws PackageParserException {
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
         if (mCachedAssetManager != null) {
             return mCachedAssetManager;
         }
 
-        ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
-                ? mSplitCodePaths.length : 0) + 1];
+        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);
 
-        // Load the base.
-        int splitIdx = 0;
-        apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+                for (String apkPath : mSplitCodePaths) {
+                    loadApkIntoAssetManager(assets, apkPath, mFlags);
+                }
+            }
 
-        // Load any splits.
-        if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
-            for (String apkPath : mSplitCodePaths) {
-                apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
+            mCachedAssetManager = assets;
+            assets = null;
+            return mCachedAssetManager;
+        } finally {
+            if (assets != null) {
+                IoUtils.closeQuietly(assets);
             }
         }
-
-        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;
-        return mCachedAssetManager;
     }
 
     @Override
-    public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
+    public AssetManager getSplitAssetManager(int splitIdx)
+            throws PackageParser.PackageParserException {
         return getBaseAssetManager();
     }
 
     @Override
     public void close() throws Exception {
-        IoUtils.closeQuietly(mCachedAssetManager);
+        if (mCachedAssetManager != null) {
+            IoUtils.closeQuietly(mCachedAssetManager);
+        }
     }
 }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 58eaabf..16023f0 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -15,21 +15,17 @@
  */
 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;
 
@@ -38,15 +34,17 @@
  * is to be used when an application opts-in to isolated split loading.
  * @hide
  */
-public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
+public class SplitAssetDependencyLoader
+        extends SplitDependencyLoader<PackageParser.PackageParserException>
         implements SplitAssetLoader {
     private final String[] mSplitPaths;
-    private final @ParseFlags int mFlags;
-    private final ApkAssets[][] mCachedSplitApks;
-    private final AssetManager[] mCachedAssetManagers;
+    private final int mFlags;
+
+    private String[][] mCachedPaths;
+    private AssetManager[] mCachedAssetManagers;
 
     public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
-            SparseArray<int[]> dependencies, @ParseFlags int flags) {
+            SparseArray<int[]> dependencies, int flags) {
         super(dependencies);
 
         // The base is inserted into index 0, so we need to shift all the splits by 1.
@@ -55,7 +53,7 @@
         System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
 
         mFlags = flags;
-        mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
+        mCachedPaths = new String[mSplitPaths.length][];
         mCachedAssetManagers = new AssetManager[mSplitPaths.length];
     }
 
@@ -64,60 +62,58 @@
         return mCachedAssetManagers[splitIdx] != null;
     }
 
-    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);
-        }
-
-        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) {
+    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
+            throws PackageParser.PackageParserException {
         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;
+        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);
+                }
+
+                if (assets.addAssetPath(assetPath) == 0) {
+                    throw new PackageParser.PackageParserException(
+                            INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Failed adding asset path: " + assetPath);
+                }
+            }
+            return assets;
+        } catch (Throwable e) {
+            IoUtils.closeQuietly(assets);
+            throw e;
+        }
     }
 
     @Override
     protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
-            int parentSplitIdx) throws PackageParserException {
-        final ArrayList<ApkAssets> assets = new ArrayList<>();
-
-        // Include parent ApkAssets.
+            int parentSplitIdx) throws PackageParser.PackageParserException {
+        final ArrayList<String> assetPaths = new ArrayList<>();
         if (parentSplitIdx >= 0) {
-            Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
+            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
         }
 
-        // Include this ApkAssets.
-        assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
-
-        // Load and include all config splits for this feature.
+        assetPaths.add(mSplitPaths[splitIdx]);
         for (int configSplitIdx : configSplitIndices) {
-            assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
+            assetPaths.add(mSplitPaths[configSplitIdx]);
         }
-
-        // Cache the results.
-        mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
-        mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
+        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
+                mFlags);
     }
 
     @Override
-    public AssetManager getBaseAssetManager() throws PackageParserException {
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
         loadDependenciesForSplit(0);
         return mCachedAssetManagers[0];
     }
 
     @Override
-    public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
+    public AssetManager getSplitAssetManager(int idx) throws PackageParser.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);
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index fd664bc..b087c48 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -33,8 +33,8 @@
  * making the creation of AssetManagers very cheap.
  * @hide
  */
-public final class ApkAssets {
-    @GuardedBy("this") private final long mNativePtr;
+public final class ApkAssets implements AutoCloseable {
+    @GuardedBy("this") private long mNativePtr;
     @GuardedBy("this") private StringBlock mStringBlock;
 
     /**
@@ -127,12 +127,14 @@
 
     @NonNull String getAssetPath() {
         synchronized (this) {
+            ensureValidLocked();
             return nativeGetAssetPath(mNativePtr);
         }
     }
 
     CharSequence getStringFromPool(int idx) {
         synchronized (this) {
+            ensureValidLocked();
             return mStringBlock.get(idx);
         }
     }
@@ -149,6 +151,7 @@
     public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
         Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
+            ensureValidLocked();
             long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
             try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
                 XmlResourceParser parser = block.newParser();
@@ -167,13 +170,41 @@
      */
     public boolean isUpToDate() {
         synchronized (this) {
+            ensureValidLocked();
             return nativeIsUpToDate(mNativePtr);
         }
     }
 
+    /**
+     * Closes the ApkAssets and destroys the underlying native implementation. Further use of the
+     * ApkAssets object will cause exceptions to be thrown.
+     *
+     * Calling close on an already closed ApkAssets does nothing.
+     */
+    @Override
+    public void close() {
+        synchronized (this) {
+            if (mNativePtr == 0) {
+                return;
+            }
+
+            mStringBlock = null;
+            nativeDestroy(mNativePtr);
+            mNativePtr = 0;
+        }
+    }
+
     @Override
     protected void finalize() throws Throwable {
-        nativeDestroy(mNativePtr);
+        if (mNativePtr != 0) {
+            nativeDestroy(mNativePtr);
+        }
+    }
+
+    private void ensureValidLocked() {
+        if (mNativePtr == 0) {
+            throw new RuntimeException("ApkAssets is closed");
+        }
     }
 
     private static native long nativeLoad(
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index c59e6d2..4f614a7 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -59,8 +59,6 @@
 
     private static final Object sSync = new Object();
 
-    private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0];
-
     // Not private for LayoutLib's BridgeAssetManager.
     @GuardedBy("sSync") static AssetManager sSystem = null;
 
@@ -216,16 +214,10 @@
      */
     public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
         Preconditions.checkNotNull(apkAssets, "apkAssets");
-
-        // Copy the apkAssets, but prepend the system assets (framework + overlays).
-        final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length];
-        System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);
-        System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length);
-
         synchronized (this) {
-            ensureOpenLocked();
-            mApkAssets = newApkAssets;
-            nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
+            ensureValidLocked();
+            mApkAssets = apkAssets;
+            nativeSetApkAssets(mObject, apkAssets, invalidateCaches);
             if (invalidateCaches) {
                 // Invalidate all caches.
                 invalidateCachesLocked(-1);
@@ -244,37 +236,13 @@
     }
 
     /**
-     * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this
-     * returns a 0-length array.
      * @hide
      */
     public @NonNull ApkAssets[] getApkAssets() {
         synchronized (this) {
-            if (mOpen) {
-                return mApkAssets;
-            }
-        }
-        return sEmptyApkAssets;
-    }
-
-    /**
-     * Returns a cookie for use with the other APIs of AssetManager.
-     * @return 0 if the path was not found, otherwise a positive integer cookie representing
-     * this path in the AssetManager.
-     * @hide
-     */
-    public int findCookieForPath(@NonNull String path) {
-        Preconditions.checkNotNull(path, "path");
-        synchronized (this) {
             ensureValidLocked();
-            final int count = mApkAssets.length;
-            for (int i = 0; i < count; i++) {
-                if (path.equals(mApkAssets[i].getAssetPath())) {
-                    return i + 1;
-                }
-            }
+            return mApkAssets;
         }
-        return 0;
     }
 
     /**
@@ -354,7 +322,6 @@
      * then this implies that ensureValidLocked() also passes.
      */
     private void ensureOpenLocked() {
-        // If mOpen is true, this implies that mObject != 0.
         if (!mOpen) {
             throw new RuntimeException("AssetManager has been closed");
         }
@@ -1173,7 +1140,6 @@
         if (mNumRefs == 0 && mObject != 0) {
             nativeDestroy(mObject);
             mObject = 0;
-            mApkAssets = sEmptyApkAssets;
         }
     }