Add support for configForSplit

Applications with the android:isolatedSplits="true" attribute in
their AndroidManifest.xml would have their Split APKs loaded in
isolation of each other, based on a set of dependencies.

Configuration Splits generated for a Feature split would not be properly
loaded before, so this change, along with a tools change, fixes this
issue and completes support for isolatedSplits.

Bug: 30999713
Test: CTS test coming (depends on some tool changes)
Change-Id: Ia4e7b0e69168a9d6637867558e306f7031720fb3
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ede9281..8a3d9b1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -156,13 +156,13 @@
     @GuardedBy("ContextImpl.class")
     private ArrayMap<String, File> mSharedPrefsPaths;
 
-    final ActivityThread mMainThread;
-    final LoadedApk mPackageInfo;
-    private ClassLoader mClassLoader;
+    final @NonNull ActivityThread mMainThread;
+    final @NonNull LoadedApk mPackageInfo;
+    private @Nullable ClassLoader mClassLoader;
 
-    private final IBinder mActivityToken;
+    private final @Nullable IBinder mActivityToken;
 
-    private final UserHandle mUser;
+    private final @Nullable UserHandle mUser;
 
     private final ApplicationContentResolver mContentResolver;
 
@@ -181,6 +181,9 @@
     private PackageManager mPackageManager;
     private Context mReceiverRestrictedContext = null;
 
+    // The name of the split this Context is representing. May be null.
+    private @Nullable String mSplitName = null;
+
     private final Object mSync = new Object();
 
     @GuardedBy("mSync")
@@ -1914,17 +1917,25 @@
         }
     }
 
-    private static Resources createResources(IBinder activityToken, LoadedApk pi, int displayId,
-            Configuration overrideConfig, CompatibilityInfo compatInfo) {
+    private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
+            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
+        final String[] splitResDirs;
+        final ClassLoader classLoader;
+        try {
+            splitResDirs = pi.getSplitPaths(splitName);
+            classLoader = pi.getSplitClassLoader(splitName);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
         return ResourcesManager.getInstance().getResources(activityToken,
                 pi.getResDir(),
-                pi.getSplitResDirs(),
+                splitResDirs,
                 pi.getOverlayDirs(),
                 pi.getApplicationInfo().sharedLibraryFiles,
                 displayId,
                 overrideConfig,
                 compatInfo,
-                pi.getClassLoader());
+                classLoader);
     }
 
     @Override
@@ -1933,14 +1944,13 @@
         LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
-                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
-                    null);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
+                    new UserHandle(UserHandle.getUserId(application.uid)), flags, null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
 
-            c.setResources(createResources(mActivityToken, pi, displayId, null,
+            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -1964,20 +1974,20 @@
         if (packageName.equals("system") || packageName.equals("android")) {
             // The system resources are loaded in every application, so we can safely copy
             // the context without reloading Resources.
-            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags,
-                    null);
+            return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,
+                    flags, null);
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags,
-                    null);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
+                    flags, null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
 
-            c.setResources(createResources(mActivityToken, pi, displayId, null,
+            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -1999,7 +2009,7 @@
         final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
         final String[] paths = mPackageInfo.getSplitPaths(splitName);
 
-        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName,
                 mActivityToken, mUser, mFlags, classLoader);
 
         final int displayId = mDisplay != null
@@ -2024,11 +2034,11 @@
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags, mClassLoader);
+        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+                mActivityToken, mUser, mFlags, mClassLoader);
 
         final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-        context.setResources(createResources(mActivityToken, mPackageInfo, displayId,
+        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         return context;
     }
@@ -2039,12 +2049,12 @@
             throw new IllegalArgumentException("display must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags, mClassLoader);
+        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+                mActivityToken, mUser, mFlags, mClassLoader);
 
         final int displayId = display.getDisplayId();
-        context.setResources(createResources(mActivityToken, mPackageInfo, displayId, null,
-                getDisplayAdjustments(displayId).getCompatibilityInfo()));
+        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
+                null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         context.mDisplay = display;
         return context;
     }
@@ -2053,16 +2063,16 @@
     public Context createDeviceProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
-        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
-                mClassLoader);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
+                flags, mClassLoader);
     }
 
     @Override
     public Context createCredentialProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
-        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
-                mClassLoader);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
+                flags, mClassLoader);
     }
 
     @Override
@@ -2149,7 +2159,7 @@
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                 null);
         context.setResources(packageInfo.getResources());
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
@@ -2159,7 +2169,7 @@
 
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                 null);
         context.setResources(packageInfo.getResources());
         return context;
@@ -2186,8 +2196,8 @@
             }
         }
 
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
-                0, classLoader);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
+                activityToken, null, 0, classLoader);
 
         // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
         displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2214,9 +2224,10 @@
         return context;
     }
 
-    private ContextImpl(ContextImpl container, ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
-            ClassLoader classLoader) {
+    private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
+            @NonNull LoadedApk packageInfo, @Nullable String splitName,
+            @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
+            @Nullable ClassLoader classLoader) {
         mOuterContext = this;
 
         // If creator didn't specify which storage to use, use the default
@@ -2241,6 +2252,7 @@
         mUser = user;
 
         mPackageInfo = packageInfo;
+        mSplitName = splitName;
         mClassLoader = classLoader;
         mResourcesManager = ResourcesManager.getInstance();
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index cf41e4e..dbed1be 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -27,7 +27,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.split.SplitDependencyLoaderHelper;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.split.SplitDependencyLoader;
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Resources;
@@ -49,7 +50,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
@@ -304,7 +304,7 @@
                 final String[] splitPaths;
                 try {
                     splitPaths = getSplitPaths(null);
-                } catch (PackageManager.NameNotFoundException e) {
+                } catch (NameNotFoundException e) {
                     // This should NEVER fail.
                     throw new AssertionError("null split not found");
                 }
@@ -336,7 +336,7 @@
         mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
 
         if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
-            mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+            mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
         }
     }
 
@@ -465,110 +465,88 @@
         }
     }
 
-    private class SplitDependencyLoader
-            extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
-        private String[] mCachedBaseResourcePath;
+    /*
+     * All indices received by the super class should be shifted by 1 when accessing mSplitNames,
+     * etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't
+     * include the base APK in the list of splits.
+     */
+    private class SplitDependencyLoaderImpl extends SplitDependencyLoader<NameNotFoundException> {
         private final String[][] mCachedResourcePaths;
-        private final ClassLoader[] mCachedSplitClassLoaders;
+        private final ClassLoader[] mCachedClassLoaders;
 
-        SplitDependencyLoader(SparseIntArray dependencies) {
+        SplitDependencyLoaderImpl(@NonNull SparseArray<int[]> dependencies) {
             super(dependencies);
-            mCachedResourcePaths = new String[mSplitNames.length][];
-            mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+            mCachedResourcePaths = new String[mSplitNames.length + 1][];
+            mCachedClassLoaders = new ClassLoader[mSplitNames.length + 1];
         }
 
         @Override
         protected boolean isSplitCached(int splitIdx) {
-            if (splitIdx != -1) {
-                return mCachedSplitClassLoaders[splitIdx] != null;
-            }
-            return mClassLoader != null && mCachedBaseResourcePath != null;
-        }
-
-        private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
-            for (int i = 0; i < mSplitNames.length; i++) {
-                if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
-                    outAssetPaths.add(mSplitResDirs[i]);
-                }
-            }
+            return mCachedClassLoaders[splitIdx] != null;
         }
 
         @Override
-        protected void constructSplit(int splitIdx, int parentSplitIdx) throws
-                PackageManager.NameNotFoundException {
+        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+                int parentSplitIdx) throws NameNotFoundException {
             final ArrayList<String> splitPaths = new ArrayList<>();
-            if (splitIdx == -1) {
+            if (splitIdx == 0) {
                 createOrUpdateClassLoaderLocked(null);
-                addAllConfigSplits(null, splitPaths);
-                mCachedBaseResourcePath = splitPaths.toArray(new String[splitPaths.size()]);
+                mCachedClassLoaders[0] = mClassLoader;
+
+                // Never add the base resources here, they always get added no matter what.
+                for (int configSplitIdx : configSplitIndices) {
+                    splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
+                }
+                mCachedResourcePaths[0] = splitPaths.toArray(new String[splitPaths.size()]);
                 return;
             }
 
-            final ClassLoader parent;
-            if (parentSplitIdx == -1) {
-                // The parent is the base APK, so use its ClassLoader as parent
-                // and its configuration splits as part of our own too.
-                parent = mClassLoader;
-                Collections.addAll(splitPaths, mCachedBaseResourcePath);
-            } else {
-                parent = mCachedSplitClassLoaders[parentSplitIdx];
-                Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+            // Since we handled the special base case above, parentSplitIdx is always valid.
+            final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
+            mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
+                    mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent);
+
+            Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+            splitPaths.add(mSplitResDirs[splitIdx - 1]);
+            for (int configSplitIdx : configSplitIndices) {
+                splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
             }
-
-            mCachedSplitClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
-                    mSplitAppDirs[splitIdx], getTargetSdkVersion(), false, null, null, parent);
-
-            splitPaths.add(mSplitResDirs[splitIdx]);
-            addAllConfigSplits(mSplitNames[splitIdx], splitPaths);
             mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
         }
 
-        private int ensureSplitLoaded(String splitName)
-                throws PackageManager.NameNotFoundException {
-            final int idx;
-            if (splitName == null) {
-                idx = -1;
-            } else {
+        private int ensureSplitLoaded(String splitName) throws NameNotFoundException {
+            int idx = 0;
+            if (splitName != null) {
                 idx = Arrays.binarySearch(mSplitNames, splitName);
                 if (idx < 0) {
                     throw new PackageManager.NameNotFoundException(
                             "Split name '" + splitName + "' is not installed");
                 }
+                idx += 1;
             }
-
             loadDependenciesForSplit(idx);
             return idx;
         }
 
-        ClassLoader getClassLoaderForSplit(String splitName)
-                throws PackageManager.NameNotFoundException {
-            final int idx = ensureSplitLoaded(splitName);
-            if (idx < 0) {
-                return mClassLoader;
-            }
-            return mCachedSplitClassLoaders[idx];
+        ClassLoader getClassLoaderForSplit(String splitName) throws NameNotFoundException {
+            return mCachedClassLoaders[ensureSplitLoaded(splitName)];
         }
 
-        String[] getSplitPathsForSplit(String splitName)
-                throws PackageManager.NameNotFoundException {
-            final int idx = ensureSplitLoaded(splitName);
-            if (idx < 0) {
-                return mCachedBaseResourcePath;
-            }
-            return mCachedResourcePaths[idx];
+        String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
+            return mCachedResourcePaths[ensureSplitLoaded(splitName)];
         }
     }
 
-    private SplitDependencyLoader mSplitLoader;
+    private SplitDependencyLoaderImpl mSplitLoader;
 
-    ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+    ClassLoader getSplitClassLoader(String splitName) throws NameNotFoundException {
         if (mSplitLoader == null) {
             return mClassLoader;
         }
         return mSplitLoader.getClassLoaderForSplit(splitName);
     }
 
-    String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+    String[] getSplitPaths(String splitName) throws NameNotFoundException {
         if (mSplitLoader == null) {
             return mSplitResDirs;
         }
@@ -925,7 +903,7 @@
             final String[] splitPaths;
             try {
                 splitPaths = getSplitPaths(null);
-            } catch (PackageManager.NameNotFoundException e) {
+            } catch (NameNotFoundException e) {
                 // This should never fail.
                 throw new AssertionError("null split not found");
             }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b4d77a0..0b3742f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,7 +31,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -656,13 +656,15 @@
      *
      * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
      * and {@link #splitPublicSourceDirs} arrays.
-     * Each key represents a split and its value is its parent split.
+     * Each key represents a split and its value is an array of splits. The first element of this
+     * array is the parent split, and the rest are configuration splits. These configuration splits
+     * have no dependencies themselves.
      * Cycles do not exist because they are illegal and screened for during installation.
      *
      * May be null if no splits are installed, or if no dependencies exist between them.
      * @hide
      */
-    public SparseIntArray splitDependencies;
+    public SparseArray<int[]> splitDependencies;
 
     /**
      * Full paths to the locations of extra resource packages (runtime overlays)
@@ -1182,7 +1184,7 @@
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
-        dest.writeSparseIntArray(splitDependencies);
+        dest.writeSparseArray((SparseArray) splitDependencies);
         dest.writeString(nativeLibraryDir);
         dest.writeString(secondaryNativeLibraryDir);
         dest.writeString(nativeLibraryRootDir);
@@ -1244,7 +1246,7 @@
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
-        splitDependencies = source.readSparseIntArray();
+        splitDependencies = source.readSparseArray(null);
         nativeLibraryDir = source.readString();
         secondaryNativeLibraryDir = source.readString();
         nativeLibraryRootDir = source.readString();
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 59c5307..f6f1be6 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -82,13 +82,15 @@
      *
      * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
      * and {@link #splitPublicSourceDirs} arrays.
-     * Each key represents a split and its value is its parent split.
+     * Each key represents a split and its value is an array of splits. The first element of this
+     * array is the parent split, and the rest are configuration splits. These configuration splits
+     * have no dependencies themselves.
      * Cycles do not exist because they are illegal and screened for during installation.
      *
      * May be null if no splits are installed, or if no dependencies exist between them.
      * @hide
      */
-    public SparseIntArray splitDependencies;
+    public SparseArray<int[]> splitDependencies;
 
     /**
      * Full path to a directory assigned to the package for its persistent data.
@@ -155,7 +157,7 @@
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
-        dest.writeSparseIntArray(splitDependencies);
+        dest.writeSparseArray((SparseArray) splitDependencies);
         dest.writeString(dataDir);
         dest.writeString(deviceProtectedDataDir);
         dest.writeString(credentialProtectedDataDir);
@@ -185,7 +187,7 @@
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
-        splitDependencies = source.readSparseIntArray();
+        splitDependencies = source.readSparseArray(null);
         dataDir = source.readString();
         deviceProtectedDataDir = source.readString();
         credentialProtectedDataDir = source.readString();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5a28e87..805c823 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -76,7 +76,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.jar.StrictJarFile;
@@ -109,7 +109,6 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -368,8 +367,12 @@
         /** Names of any split APKs, ordered by parsed splitName */
         public final String[] splitNames;
 
+        /** Names of any split APKs that are features. Ordered by splitName */
+        public final boolean[] isFeatureSplits;
+
         /** Dependencies of any split APKs, ordered by parsed splitName */
         public final String[] usesSplitNames;
+        public final String[] configForSplit;
 
         /**
          * Path where this package was found on disk. For monolithic packages
@@ -396,14 +399,16 @@
         public final boolean isolatedSplits;
 
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
-                String[] usesSplitNames, String[] splitCodePaths,
-                int[] splitRevisionCodes) {
+                boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit,
+                String[] splitCodePaths, int[] splitRevisionCodes) {
             this.packageName = baseApk.packageName;
             this.versionCode = baseApk.versionCode;
             this.installLocation = baseApk.installLocation;
             this.verifiers = baseApk.verifiers;
             this.splitNames = splitNames;
+            this.isFeatureSplits = isFeatureSplits;
             this.usesSplitNames = usesSplitNames;
+            this.configForSplit = configForSplit;
             this.codePath = codePath;
             this.baseCodePath = baseApk.codePath;
             this.splitCodePaths = splitCodePaths;
@@ -434,6 +439,8 @@
         public final String codePath;
         public final String packageName;
         public final String splitName;
+        public boolean isFeatureSplit;
+        public final String configForSplit;
         public final String usesSplitName;
         public final int versionCode;
         public final int revisionCode;
@@ -448,14 +455,17 @@
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
 
-        public ApkLite(String codePath, String packageName, String splitName, String usesSplitName,
-                int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers,
-                Signature[] signatures, Certificate[][] certificates, boolean coreApp,
-                boolean debuggable, boolean multiArch, boolean use32bitAbi,
-                boolean extractNativeLibs, boolean isolatedSplits) {
+        public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+                String configForSplit, String usesSplitName, int versionCode, int revisionCode,
+                int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
+                Certificate[][] certificates, boolean coreApp, boolean debuggable,
+                boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs,
+                boolean isolatedSplits) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
+            this.isFeatureSplit = isFeatureSplit;
+            this.configForSplit = configForSplit;
             this.usesSplitName = usesSplitName;
             this.versionCode = versionCode;
             this.revisionCode = revisionCode;
@@ -811,10 +821,10 @@
         final ApkLite baseApk = parseApkLite(packageFile, flags);
         final String packagePath = packageFile.getAbsolutePath();
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        return new PackageLite(packagePath, baseApk, null, null, null, null);
+        return new PackageLite(packagePath, baseApk, null, null, null, null, null, null);
     }
 
-    private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+    static PackageLite parseClusterPackageLite(File packageDir, int flags)
             throws PackageParserException {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -869,12 +879,16 @@
         final int size = apks.size();
 
         String[] splitNames = null;
+        boolean[] isFeatureSplits = null;
         String[] usesSplitNames = null;
+        String[] configForSplits = null;
         String[] splitCodePaths = null;
         int[] splitRevisionCodes = null;
         if (size > 0) {
             splitNames = new String[size];
+            isFeatureSplits = new boolean[size];
             usesSplitNames = new String[size];
+            configForSplits = new String[size];
             splitCodePaths = new String[size];
             splitRevisionCodes = new int[size];
 
@@ -884,14 +898,16 @@
             for (int i = 0; i < size; i++) {
                 final ApkLite apk = apks.get(splitNames[i]);
                 usesSplitNames[i] = apk.usesSplitName;
+                isFeatureSplits[i] = apk.isFeatureSplit;
+                configForSplits[i] = apk.configForSplit;
                 splitCodePaths[i] = apk.codePath;
                 splitRevisionCodes[i] = apk.revisionCode;
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
-        return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
-                splitRevisionCodes);
+        return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames,
+                configForSplits, splitCodePaths, splitRevisionCodes);
     }
 
     /**
@@ -1069,42 +1085,6 @@
         }
     }
 
-    private static SparseIntArray buildSplitDependencyTree(PackageLite pkg)
-            throws PackageParserException {
-        SparseIntArray splitDependencies = new SparseIntArray();
-        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
-            final String splitDependency = pkg.usesSplitNames[splitIdx];
-            if (splitDependency != null) {
-                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
-                if (depIdx < 0) {
-                    throw new PackageParserException(
-                            PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                            "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
-                                    + splitDependency + "', which is missing.");
-                }
-                splitDependencies.put(splitIdx, depIdx);
-            }
-        }
-
-        // Verify that there are no cycles.
-        final BitSet bitset = new BitSet();
-        for (int i = 0; i < splitDependencies.size(); i++) {
-            int splitIdx = splitDependencies.keyAt(i);
-
-            bitset.clear();
-            while (splitIdx != -1) {
-                if (bitset.get(splitIdx)) {
-                    throw new PackageParserException(
-                            PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                            "Cycle detected in split dependencies.");
-                }
-                bitset.set(splitIdx);
-                splitIdx = splitDependencies.get(splitIdx, -1);
-            }
-        }
-        return splitDependencies.size() != 0 ? splitDependencies : null;
-    }
-
     /**
      * Parse all APKs contained in the given directory, treating them as a
      * single package. This also performs sanity checking, such as requiring
@@ -1122,11 +1102,15 @@
         }
 
         // Build the split dependency tree.
-        SparseIntArray splitDependencies = null;
+        SparseArray<int[]> splitDependencies = null;
         final SplitAssetLoader assetLoader;
         if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
-            splitDependencies = buildSplitDependencyTree(lite);
-            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+            try {
+                splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
+                assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+            } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
+            }
         } else {
             assetLoader = new DefaultSplitAssetLoader(lite, flags);
         }
@@ -1762,6 +1746,8 @@
         boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
         boolean isolatedSplits = false;
+        boolean isFeatureSplit = false;
+        String configForSplit = null;
         String usesSplitName = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1777,6 +1763,10 @@
                 coreApp = attrs.getAttributeBooleanValue(i, false);
             } else if (attr.equals("isolatedSplits")) {
                 isolatedSplits = attrs.getAttributeBooleanValue(i, false);
+            } else if (attr.equals("configForSplit")) {
+                configForSplit = attrs.getAttributeValue(i);
+            } else if (attr.equals("isFeatureSplit")) {
+                isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
             }
         }
 
@@ -1831,10 +1821,10 @@
             }
         }
 
-        return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
-                versionCode, revisionCode, installLocation, verifiers, signatures,
-                certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
-                isolatedSplits);
+        return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+                configForSplit, usesSplitName, versionCode, revisionCode, installLocation,
+                verifiers, signatures, certificates, coreApp, debuggable, multiArch, use32bitAbi,
+                extractNativeLibs, isolatedSplits);
     }
 
     /**
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 4df90eb..16023f0 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -18,10 +18,11 @@
 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.PackageParser;
 import android.content.res.AssetManager;
 import android.os.Build;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
@@ -34,49 +35,31 @@
  * @hide
  */
 public class SplitAssetDependencyLoader
-        extends SplitDependencyLoaderHelper<PackageParser.PackageParserException>
+        extends SplitDependencyLoader<PackageParser.PackageParserException>
         implements SplitAssetLoader {
-    private static final int BASE_ASSET_PATH_IDX = -1;
-    private final String mBasePath;
-    private final String[] mSplitNames;
     private final String[] mSplitPaths;
     private final int mFlags;
 
-    private String[] mCachedBasePaths;
-    private AssetManager mCachedBaseAssetManager;
-
-    private String[][] mCachedSplitPaths;
+    private String[][] mCachedPaths;
     private AssetManager[] mCachedAssetManagers;
 
-    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
-            int flags) {
+    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+            SparseArray<int[]> dependencies, int flags) {
         super(dependencies);
-        mBasePath = pkg.baseCodePath;
-        mSplitNames = pkg.splitNames;
-        mSplitPaths = pkg.splitCodePaths;
+
+        // The base is inserted into index 0, so we need to shift all the splits by 1.
+        mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+        mSplitPaths[0] = pkg.baseCodePath;
+        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
         mFlags = flags;
-        mCachedBasePaths = null;
-        mCachedBaseAssetManager = null;
-        mCachedSplitPaths = new String[mSplitNames.length][];
-        mCachedAssetManagers = new AssetManager[mSplitNames.length];
+        mCachedPaths = new String[mSplitPaths.length][];
+        mCachedAssetManagers = new AssetManager[mSplitPaths.length];
     }
 
     @Override
     protected boolean isSplitCached(int splitIdx) {
-        if (splitIdx != -1) {
-            return mCachedAssetManagers[splitIdx] != null;
-        }
-        return mCachedBaseAssetManager != null;
-    }
-
-    // Adds all non-code configuration splits for this split name. The split name is expected
-    // to represent a feature split.
-    private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
-        for (int i = 0; i < mSplitNames.length; i++) {
-            if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
-                outAssetPaths.add(mSplitPaths[i]);
-            }
-        }
+        return mCachedAssetManagers[splitIdx] != null;
     }
 
     private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
@@ -107,45 +90,38 @@
     }
 
     @Override
-    protected void constructSplit(int splitIdx, int parentSplitIdx) throws
-            PackageParser.PackageParserException {
+    protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+            int parentSplitIdx) throws PackageParser.PackageParserException {
         final ArrayList<String> assetPaths = new ArrayList<>();
-        if (splitIdx == BASE_ASSET_PATH_IDX) {
-            assetPaths.add(mBasePath);
-            addAllConfigSplits(null, assetPaths);
-            mCachedBasePaths = assetPaths.toArray(new String[assetPaths.size()]);
-            mCachedBaseAssetManager = createAssetManagerWithPaths(mCachedBasePaths, mFlags);
-            return;
-        }
-
-        if (parentSplitIdx == BASE_ASSET_PATH_IDX) {
-            Collections.addAll(assetPaths, mCachedBasePaths);
-        } else {
-            Collections.addAll(assetPaths, mCachedSplitPaths[parentSplitIdx]);
+        if (parentSplitIdx >= 0) {
+            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
         }
 
         assetPaths.add(mSplitPaths[splitIdx]);
-        addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
-        mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
-        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+        for (int configSplitIdx : configSplitIndices) {
+            assetPaths.add(mSplitPaths[configSplitIdx]);
+        }
+        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
                 mFlags);
     }
 
     @Override
     public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
-        loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
-        return mCachedBaseAssetManager;
+        loadDependenciesForSplit(0);
+        return mCachedAssetManagers[0];
     }
 
     @Override
     public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
-        loadDependenciesForSplit(idx);
-        return mCachedAssetManagers[idx];
+        // 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);
+        return mCachedAssetManagers[idx + 1];
     }
 
     @Override
     public void close() throws Exception {
-        IoUtils.closeQuietly(mCachedBaseAssetManager);
         for (AssetManager assets : mCachedAssetManagers) {
             IoUtils.closeQuietly(assets);
         }
diff --git a/core/java/android/content/pm/split/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java
new file mode 100644
index 0000000..3586546
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoader.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.split;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.content.pm.PackageParser;
+import android.util.IntArray;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ */
+public abstract class SplitDependencyLoader<E extends Exception> {
+    private final @NonNull SparseArray<int[]> mDependencies;
+
+    /**
+     * Construct a new SplitDependencyLoader. Meant to be called from the
+     * subclass constructor.
+     * @param dependencies The dependency tree of splits.
+     */
+    protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+        mDependencies = dependencies;
+    }
+
+    /**
+     * Traverses the dependency tree and constructs any splits that are not already
+     * cached. This routine short-circuits and skips the creation of splits closer to the
+     * root if they are cached, as reported by the subclass implementation of
+     * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+     * implementation of {@link #constructSplit(int, int[], int)}.
+     * @param splitIdx The index of the split to load. 0 represents the base Application.
+     */
+    protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+        // Quick check before any allocations are done.
+        if (isSplitCached(splitIdx)) {
+            return;
+        }
+
+        // Special case the base, since it has no dependencies.
+        if (splitIdx == 0) {
+            final int[] configSplitIndices = collectConfigSplitIndices(0);
+            constructSplit(0, configSplitIndices, -1);
+            return;
+        }
+
+        // Build up the dependency hierarchy.
+        final IntArray linearDependencies = new IntArray();
+        linearDependencies.add(splitIdx);
+
+        // Collect all the dependencies that need to be constructed.
+        // They will be listed from leaf to root.
+        while (true) {
+            // Only follow the first index into the array. The others are config splits and
+            // get loaded with the split.
+            final int[] deps = mDependencies.get(splitIdx);
+            if (deps != null && deps.length > 0) {
+                splitIdx = deps[0];
+            } else {
+                splitIdx = -1;
+            }
+
+            if (splitIdx < 0 || isSplitCached(splitIdx)) {
+                break;
+            }
+
+            linearDependencies.add(splitIdx);
+        }
+
+        // Visit each index, from right to left (root to leaf).
+        int parentIdx = splitIdx;
+        for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+            final int idx = linearDependencies.get(i);
+            final int[] configSplitIndices = collectConfigSplitIndices(idx);
+            constructSplit(idx, configSplitIndices, parentIdx);
+            parentIdx = idx;
+        }
+    }
+
+    private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+        // The config splits appear after the first element.
+        final int[] deps = mDependencies.get(splitIdx);
+        if (deps == null || deps.length <= 1) {
+            return EmptyArray.INT;
+        }
+        return Arrays.copyOfRange(deps, 1, deps.length);
+    }
+
+    /**
+     * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+     * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+     * @param splitIdx The index of the split to check for in the cache.
+     * @return true if the split is cached and does not need to be constructed.
+     */
+    protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+    /**
+     * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+     * The result is expected to be cached by the subclass in its own structures.
+     * @param splitIdx The index of the split to construct. 0 represents the base Application.
+     * @param configSplitIndices The array of configuration splits to load along with this split.
+     *                           May be empty (length == 0) but never null.
+     * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+     * @throws E Subclass defined exception representing failure to construct a split.
+     */
+    protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+            @NonNull @IntRange(from = 1) int[] configSplitIndices,
+            @IntRange(from = -1) int parentSplitIdx) throws E;
+
+    public static class IllegalDependencyException extends Exception {
+        private IllegalDependencyException(String message) {
+            super(message);
+        }
+    }
+
+    private static int[] append(int[] src, int elem) {
+        if (src == null) {
+            return new int[] { elem };
+        }
+        int[] dst = Arrays.copyOf(src, src.length + 1);
+        dst[src.length] = elem;
+        return dst;
+    }
+
+    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+            PackageParser.PackageLite pkg) throws IllegalDependencyException {
+        // The data structure that holds the dependencies. In PackageParser, splits are stored
+        // in their own array, separate from the base. We treat all paths as equals, so
+        // we need to insert the base as index 0, and shift all other splits.
+        final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+        // The base depends on nothing.
+        splitDependencies.put(0, new int[] {-1});
+
+        // First write out the <uses-split> dependencies. These must appear first in the
+        // array of ints, as is convention in this class.
+        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+            if (!pkg.isFeatureSplits[splitIdx]) {
+                // Non-feature splits don't have dependencies.
+                continue;
+            }
+
+            // Implicit dependency on the base.
+            final int targetIdx;
+            final String splitDependency = pkg.usesSplitNames[splitIdx];
+            if (splitDependency != null) {
+                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                if (depIdx < 0) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' requires split '" + splitDependency + "', which is missing.");
+                }
+                targetIdx = depIdx + 1;
+            } else {
+                // Implicitly depend on the base.
+                targetIdx = 0;
+            }
+            splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+        }
+
+        // Write out the configForSplit reverse-dependencies. These appear after the <uses-split>
+        // dependencies and are considered leaves.
+        //
+        // At this point, all splits in splitDependencies have the first element in their array set.
+        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+            if (pkg.isFeatureSplits[splitIdx]) {
+                // Feature splits are not configForSplits.
+                continue;
+            }
+
+            // Implicit feature for the base.
+            final int targetSplitIdx;
+            final String configForSplit = pkg.configForSplit[splitIdx];
+            if (configForSplit != null) {
+                final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                if (depIdx < 0) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' targets split '" + configForSplit + "', which is missing.");
+                }
+
+                if (!pkg.isFeatureSplits[depIdx]) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' declares itself as configuration split for a non-feature split '"
+                            + pkg.splitNames[depIdx] + "'");
+                }
+                targetSplitIdx = depIdx + 1;
+            } else {
+                targetSplitIdx = 0;
+            }
+            splitDependencies.put(targetSplitIdx,
+                    append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+        }
+
+        // Verify that there are no cycles.
+        final BitSet bitset = new BitSet();
+        for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+            int splitIdx = splitDependencies.keyAt(i);
+
+            bitset.clear();
+            while (splitIdx != -1) {
+                // Check if this split has been visited yet.
+                if (bitset.get(splitIdx)) {
+                    throw new IllegalDependencyException("Cycle detected in split dependencies.");
+                }
+
+                // Mark the split so that if we visit it again, we no there is a cycle.
+                bitset.set(splitIdx);
+
+                // Follow the first dependency only, the others are leaves by definition.
+                final int[] deps = splitDependencies.get(splitIdx);
+                splitIdx = deps != null ? deps[0] : -1;
+            }
+        }
+        return splitDependencies;
+    }
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
deleted file mode 100644
index b493480..0000000
--- a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.pm.split;
-
-import android.annotation.Nullable;
-import android.util.IntArray;
-import android.util.SparseIntArray;
-
-/**
- * A helper class that implements the dependency tree traversal for splits. Callbacks
- * are implemented by subclasses to notify whether a split has already been constructed
- * and is cached, and to actually create the split requested.
- *
- * This helper is meant to be subclassed so as to reduce the number of allocations
- * needed to make use of it.
- *
- * All inputs and outputs are assumed to be indices into an array of splits.
- *
- * @hide
- */
-public abstract class SplitDependencyLoaderHelper<E extends Exception> {
-    @Nullable private final SparseIntArray mDependencies;
-
-    /**
-     * Construct a new SplitDependencyLoaderHelper. Meant to be called from the
-     * subclass constructor.
-     * @param dependencies The dependency tree of splits. Can be null, which leads to
-     *                     just the implicit dependency of all splits on the base.
-     */
-    protected SplitDependencyLoaderHelper(@Nullable SparseIntArray dependencies) {
-        mDependencies = dependencies;
-    }
-
-    /**
-     * Traverses the dependency tree and constructs any splits that are not already
-     * cached. This routine short-circuits and skips the creation of splits closer to the
-     * root if they are cached, as reported by the subclass implementation of
-     * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
-     * implementation of {@link #constructSplit(int, int)}.
-     * @param splitIdx The index of the split to load. Can be -1, which represents the
-     *                 base Application.
-     */
-    protected void loadDependenciesForSplit(int splitIdx) throws E {
-        // Quick check before any allocations are done.
-        if (isSplitCached(splitIdx)) {
-            return;
-        }
-
-        final IntArray linearDependencies = new IntArray();
-        linearDependencies.add(splitIdx);
-
-        // Collect all the dependencies that need to be constructed.
-        // They will be listed from leaf to root.
-        while (splitIdx >= 0) {
-            splitIdx = mDependencies != null ? mDependencies.get(splitIdx, -1) : -1;
-            if (isSplitCached(splitIdx)) {
-                break;
-            }
-            linearDependencies.add(splitIdx);
-        }
-
-        // Visit each index, from right to left (root to leaf).
-        int parentIdx = splitIdx;
-        for (int i = linearDependencies.size() - 1; i >= 0; i--) {
-            final int idx = linearDependencies.get(i);
-            constructSplit(idx, parentIdx);
-            parentIdx = idx;
-        }
-    }
-
-    /**
-     * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
-     * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
-     * @param splitIdx The index of the split to check for in the cache.
-     * @return true if the split is cached and does not need to be constructed.
-     */
-    protected abstract boolean isSplitCached(int splitIdx);
-
-    /**
-     * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
-     * The result is expected to be cached by the subclass in its own structures.
-     * @param splitIdx The index of the split to construct. Can be -1, which represents the
-     *                 base Application.
-     * @param parentSplitIdx The index of the parent split. Can be -1, which represents the
-     *                       base Application.
-     * @throws E
-     */
-    protected abstract void constructSplit(int splitIdx, int parentSplitIdx) throws E;
-
-    /**
-     * Returns true if `splitName` represents a Configuration split of `featureSplitName`.
-     *
-     * A Configuration split's name is prefixed with the associated Feature split's name
-     * or the empty string if the split is for the base Application APK. It is then followed by the
-     * dollar sign character "$" and some unique string that should represent the configurations
-     * the split contains.
-     *
-     * Example:
-     * <table>
-     *     <tr>
-     *         <th>Feature split name</th>
-     *         <th>Configuration split name: xhdpi</th>
-     *         <th>Configuration split name: fr-rFR</th>
-     *     </tr>
-     *     <tr>
-     *         <td>(base APK)</td>
-     *         <td><code>$xhdpi</code></td>
-     *         <td><code>$fr-rFR</code></td>
-     *     </tr>
-     *     <tr>
-     *         <td><code>Extras</code></td>
-     *         <td><code>Extras$xhdpi</code></td>
-     *         <td><code>Extras$fr-rFR</code></td>
-     *     </tr>
-     * </table>
-     *
-     * @param splitName The name of the split to check.
-     * @param featureSplitName The name of the Feature split. May be null or "" if checking
-     *                         the base Application APK.
-     * @return true if the splitName represents a Configuration split of featureSplitName.
-     */
-    protected static boolean isConfigurationSplitOf(String splitName, String featureSplitName) {
-        if (featureSplitName == null || featureSplitName.length() == 0) {
-            // We are looking for configuration splits of the base, which have some legacy support.
-            if (splitName.startsWith("config_")) {
-                return true;
-            } else if (splitName.startsWith("$")) {
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return splitName.startsWith(featureSplitName + "$");
-        }
-    }
-}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 67050f7..cf6bd9e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1014,6 +1014,15 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="isolatedSplits" format="boolean" />
 
+    <!-- If set to <code>true</code>, indicates to the platform that this APK is
+         a 'feature' split and that it implicitly depends on the base APK. This distinguishes
+         this split APK from a 'configuration' split, which provides resource overrides
+         for a particular 'feature' split. Only useful when the base APK specifies
+         <code>android:isolatedSplits="true"</code>.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="isFeatureSplit" format="boolean" />
+
     <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
          {@code <application>} tag. If specified on the {@code <application>}
          tag these will be considered defaults for all activities in the
@@ -1286,6 +1295,7 @@
         <attr name="sharedUserLabel" />
         <attr name="installLocation" />
         <attr name="isolatedSplits" />
+        <attr name="isFeatureSplit" />
         <attr name="targetSandboxVersion" />
     </declare-styleable>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2897c62..f965c69 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2807,6 +2807,7 @@
         <public name="importantForAutofill" />
         <public name="recycleEnabled"/>
         <public name="isStatic" />
+        <public name="isFeatureSplit" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">