OSDN Git Service

Add support for Split APK dependcies
authorAdam Lesinski <adamlesinski@google.com>
Tue, 22 Nov 2016 00:02:24 +0000 (16:02 -0800)
committerAdam Lesinski <adamlesinski@google.com>
Wed, 25 Jan 2017 02:34:08 +0000 (18:34 -0800)
Apps can now declare in their base APK AndroidManifest.xml
that they want to have their split APKs loaded in isolated
Contexts. This means code and resources from the split
get loaded into their own ClassLoader and AssetManager.

<manifest xmlns:android="..."
          ...
          android:isolatedSplits="true"
          ...

In order to make this more useful, splits can declare dependencies
on other splits, which will all get pulled in to the Context
and run as expected at runtime.

A split declares its dependency on another split by using the
tag <uses-split> in its AndroidManifest.xml:

<manifest xmlns:android="...">
    ...
    <uses-split android:name="feature_split_1" />
    ...

A split can have a single parent on which it depends on. This is
due to the limitation of having a single ClassLoader parent.
All splits depend on the base APK implicitly.

PackageManager verifies that no cycles exist and that each dependency
is present before allowing an installation to succeed.

The runtime will then load splits based on the dependencies.

Given the following APKs:

base <-- split A <-- split C
  ^----- split B

If an Activity defined in split C is launched, then the base,
split A, and split C will be loaded into the ClassLoader defined
for the Activity's Context. The AssetManager will similarly be loaded
with the resources of the splits.

A split can be manually loaded by creating a Context for that split, defined
by its name:

Context.createContextForSplit("my_feature_split_1");

All installed Activities, Services, Receivers, and Providers are accessible
to other apps via Intent resolution. When they are instantiated, they are
given the appropriate Context that satisfies any dependencies the split they
were defined in stipulated.

Test: WIP (CTS tests to come)
Change-Id: I8989712b241b7bc84381f2919d88455fcad62161

25 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
cmds/pm/src/com/android/commands/pm/Pm.java
core/java/android/app/ActivityThread.java
core/java/android/app/ContextImpl.java
core/java/android/app/LoadedApk.java
core/java/android/content/Context.java
core/java/android/content/ContextWrapper.java
core/java/android/content/pm/ApplicationInfo.java
core/java/android/content/pm/ComponentInfo.java
core/java/android/content/pm/InstrumentationInfo.java
core/java/android/content/pm/PackageParser.java
core/java/android/content/pm/split/DefaultSplitAssetLoader.java [new file with mode: 0644]
core/java/android/content/pm/split/SplitAssetDependencyLoader.java [new file with mode: 0644]
core/java/android/content/pm/split/SplitAssetLoader.java [new file with mode: 0644]
core/java/android/content/pm/split/SplitDependencyLoaderHelper.java [new file with mode: 0644]
core/java/android/os/Parcel.java
core/res/res/values/attrs_manifest.xml
core/res/res/values/public.xml
services/core/java/com/android/server/pm/PackageInstallerSession.java
services/core/java/com/android/server/pm/PackageManagerService.java
services/core/java/com/android/server/pm/PackageManagerShellCommand.java
test-runner/src/android/test/mock/MockContext.java
tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java

index 16cfff2..c5bde1d 100644 (file)
@@ -739,6 +739,7 @@ package android {
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -8356,6 +8357,7 @@ package android.content {
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
     method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8554,6 +8556,7 @@ package android.content {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -9738,6 +9741,7 @@ package android.content.pm {
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -9820,6 +9824,7 @@ package android.content.pm {
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -29952,6 +29957,7 @@ package android.os {
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -29996,6 +30002,7 @@ package android.os {
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -39236,6 +39243,7 @@ package android.test.mock {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
index 066f9ce..9da65a3 100644 (file)
@@ -850,6 +850,7 @@ package android {
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -8738,6 +8739,7 @@ package android.content {
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
@@ -8948,6 +8950,7 @@ package android.content {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
@@ -10153,6 +10156,7 @@ package android.content.pm {
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -10270,6 +10274,7 @@ package android.content.pm {
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -32668,6 +32673,7 @@ package android.os {
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -32712,6 +32718,7 @@ package android.os {
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -42587,6 +42594,7 @@ package android.test.mock {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
index f0c628c..c9f1d1c 100644 (file)
@@ -739,6 +739,7 @@ package android {
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -8379,6 +8380,7 @@ package android.content {
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
     method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8578,6 +8580,7 @@ package android.content {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -9765,6 +9768,7 @@ package android.content.pm {
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -9847,6 +9851,7 @@ package android.content.pm {
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -30063,6 +30068,7 @@ package android.os {
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -30107,6 +30113,7 @@ package android.os {
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -39357,6 +39364,7 @@ package android.test.mock {
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
index ac5fea3..7015381 100644 (file)
@@ -408,7 +408,7 @@ public final class Pm {
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
                     params.sessionParams.setSize(
                             PackageHelper.calculateInstalledSize(pkgLite, false,
                             params.sessionParams.abiOverride));
index d814ddc..34eaa0b 100644 (file)
@@ -2616,9 +2616,10 @@ public final class ActivityThread {
                     r.activityInfo.targetActivity);
         }
 
+        ContextImpl appContext = createBaseContextForActivity(r);
         Activity activity = null;
         try {
-            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
+            java.lang.ClassLoader cl = appContext.getClassLoader();
             activity = mInstrumentation.newActivity(
                     cl, component.getClassName(), r.intent);
             StrictMode.incrementExpectedActivityCount(activity.getClass());
@@ -2647,7 +2648,6 @@ public final class ActivityThread {
                     + ", dir=" + r.packageInfo.getAppDir());
 
             if (activity != null) {
-                Context appContext = createBaseContextForActivity(r, activity);
                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                 Configuration config = new Configuration(mCompatConfiguration);
                 if (r.overrideConfig != null) {
@@ -2661,6 +2661,7 @@ public final class ActivityThread {
                     r.mPendingRemoveWindow = null;
                     r.mPendingRemoveWindowManager = null;
                 }
+                appContext.setOuterContext(activity);
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
@@ -2736,8 +2737,8 @@ public final class ActivityThread {
         return activity;
     }
 
-    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
-        int displayId = Display.DEFAULT_DISPLAY;
+    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
+        final int displayId;
         try {
             displayId = ActivityManager.getService().getActivityDisplayId(r.token);
         } catch (RemoteException e) {
@@ -2745,9 +2746,7 @@ public final class ActivityThread {
         }
 
         ContextImpl appContext = ContextImpl.createActivityContext(
-                this, r.packageInfo, r.token, displayId, r.overrideConfig);
-        appContext.setOuterContext(activity);
-        Context baseContext = appContext;
+                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
 
         final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
         // For debugging purposes, if the activity's package name contains the value of
@@ -2760,12 +2759,12 @@ public final class ActivityThread {
                 if (id != Display.DEFAULT_DISPLAY) {
                     Display display =
                             dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
-                    baseContext = appContext.createDisplayContext(display);
+                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                     break;
                 }
             }
         }
-        return baseContext;
+        return appContext;
     }
 
     private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
@@ -3119,9 +3118,16 @@ public final class ActivityThread {
 
         IActivityManager mgr = ActivityManager.getService();
 
+        Application app;
         BroadcastReceiver receiver;
+        ContextImpl context;
         try {
-            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            app = packageInfo.makeApplication(false, mInstrumentation);
+            context = (ContextImpl) app.getBaseContext();
+            if (data.info.splitName != null) {
+                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+            }
+            java.lang.ClassLoader cl = context.getClassLoader();
             data.intent.setExtrasClassLoader(cl);
             data.intent.prepareToEnterProcess();
             data.setExtrasClassLoader(cl);
@@ -3136,8 +3142,6 @@ public final class ActivityThread {
         }
 
         try {
-            Application app = packageInfo.makeApplication(false, mInstrumentation);
-
             if (localLOGV) Slog.v(
                 TAG, "Performing receive of " + data.intent
                 + ": app=" + app
@@ -3146,7 +3150,6 @@ public final class ActivityThread {
                 + ", comp=" + data.intent.getComponent().toShortString()
                 + ", dir=" + packageInfo.getAppDir());
 
-            ContextImpl context = (ContextImpl)app.getBaseContext();
             sCurrentBroadcastIntent.set(data.intent);
             receiver.setPendingResult(data);
             receiver.onReceive(context.getReceiverRestrictedContext(),
@@ -6031,6 +6034,15 @@ public final class ActivityThread {
                       info.name);
                 return null;
             }
+
+            if (info.splitName != null) {
+                try {
+                    c = c.createContextForSplit(info.splitName);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             try {
                 final java.lang.ClassLoader cl = c.getClassLoader();
                 localProvider = (ContentProvider)cl.
index d37888d..9db2b92 100644 (file)
@@ -32,6 +32,7 @@ import android.content.IntentSender;
 import android.content.ReceiverCallNotAllowedException;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -58,21 +59,27 @@ import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
+import dalvik.system.PathClassLoader;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -80,6 +87,8 @@ import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 
 class ReceiverRestrictedContext extends ContextWrapper {
@@ -147,6 +156,7 @@ class ContextImpl extends Context {
 
     final ActivityThread mMainThread;
     final LoadedApk mPackageInfo;
+    private ClassLoader mClassLoader;
 
     private final IBinder mActivityToken;
 
@@ -272,8 +282,7 @@ class ContextImpl extends Context {
 
     @Override
     public ClassLoader getClassLoader() {
-        return mPackageInfo != null ?
-                mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
+        return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
     }
 
     @Override
@@ -1887,7 +1896,7 @@ class ContextImpl extends Context {
                 pi.getResDir(),
                 pi.getSplitResDirs(),
                 pi.getOverlayDirs(),
-                pi.getApplicationInfo().sharedLibraryFiles,
+                pi.getSharedLibraries(),
                 displayId,
                 overrideConfig,
                 compatInfo,
@@ -1901,7 +1910,8 @@ class ContextImpl extends Context {
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
-                    new UserHandle(UserHandle.getUserId(application.uid)), flags);
+                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
+                    null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1930,13 +1940,15 @@ class ContextImpl extends Context {
         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);
+            return new ContextImpl(this, mMainThread, mPackageInfo, 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);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags,
+                    null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1954,13 +1966,42 @@ class ContextImpl extends Context {
     }
 
     @Override
+    public Context createContextForSplit(String splitName) throws NameNotFoundException {
+        if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+            // All Splits are always loaded.
+            return this;
+        }
+
+        final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
+        final String[] paths = mPackageInfo.getSplitPaths(splitName);
+
+        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+                mActivityToken, mUser, mFlags, classLoader);
+
+        final int displayId = mDisplay != null
+                ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+        context.mResources = ResourcesManager.getInstance().getResources(
+                mActivityToken,
+                mPackageInfo.getResDir(),
+                paths,
+                mPackageInfo.getOverlayDirs(),
+                mPackageInfo.getApplicationInfo().sharedLibraryFiles,
+                displayId,
+                null,
+                mPackageInfo.getCompatibilityInfo(),
+                classLoader);
+        return context;
+    }
+
+    @Override
     public Context createConfigurationContext(Configuration overrideConfiguration) {
         if (overrideConfiguration == null) {
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
+                mUser, mFlags, mClassLoader);
 
         final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
         context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
@@ -1975,7 +2016,7 @@ class ContextImpl extends Context {
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
+                mUser, mFlags, mClassLoader);
 
         final int displayId = display.getDisplayId();
         context.mResources = createResources(mActivityToken, mPackageInfo, displayId, null,
@@ -1988,14 +2029,16 @@ class ContextImpl extends Context {
     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);
+        return new ContextImpl(this, mMainThread, mPackageInfo, 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);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
+                mClassLoader);
     }
 
     @Override
@@ -2082,8 +2125,9 @@ class ContextImpl extends Context {
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+                null);
+        context.mResources = packageInfo.getResources();
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                 context.mResourcesManager.getDisplayMetrics());
         return context;
@@ -2091,18 +2135,35 @@ class ContextImpl extends Context {
 
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+                null);
+        context.mResources = packageInfo.getResources();
         return context;
     }
 
     static ContextImpl createActivityContext(ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, int displayId,
+            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
             Configuration overrideConfiguration) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
 
+        String[] splitDirs = packageInfo.getSplitResDirs();
+        ClassLoader classLoader = packageInfo.getClassLoader();
+
+        if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
+            try {
+                classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
+                splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
+            } catch (NameNotFoundException e) {
+                // Nothing above us can handle a NameNotFoundException, better crash.
+                throw new RuntimeException(e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+            }
+        }
+
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
-                0);
+                0, classLoader);
 
         // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
         displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2117,20 +2178,21 @@ class ContextImpl extends Context {
         // will be rebased upon.
         context.mResources = resourcesManager.createBaseActivityResources(activityToken,
                 packageInfo.getResDir(),
-                packageInfo.getSplitResDirs(),
+                splitDirs,
                 packageInfo.getOverlayDirs(),
-                packageInfo.getApplicationInfo().sharedLibraryFiles,
+                packageInfo.getSharedLibraries(),
                 displayId,
                 overrideConfiguration,
                 compatInfo,
-                packageInfo.getClassLoader());
+                classLoader);
         context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                 context.mResources.getDisplayAdjustments());
         return context;
     }
 
     private ContextImpl(ContextImpl container, ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags) {
+            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
+            ClassLoader classLoader) {
         mOuterContext = this;
 
         // If creator didn't specify which storage to use, use the default
@@ -2155,6 +2217,7 @@ class ContextImpl extends Context {
         mUser = user;
 
         mPackageInfo = packageInfo;
+        mClassLoader = classLoader;
         mResourcesManager = ResourcesManager.getInstance();
 
         if (container != null) {
index 4ab0743..17f5edd 100644 (file)
@@ -27,9 +27,11 @@ import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.split.SplitDependencyLoaderHelper;
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -41,23 +43,22 @@ import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.ErrnoException;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.util.ArrayUtils;
+
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
@@ -65,13 +66,12 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Objects;
 
-import libcore.io.IoUtils;
-
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
         super(msg);
@@ -97,8 +97,6 @@ public final class LoadedApk {
     private ApplicationInfo mApplicationInfo;
     private String mAppDir;
     private String mResDir;
-    private String[] mSplitAppDirs;
-    private String[] mSplitResDirs;
     private String[] mOverlayDirs;
     private String[] mSharedLibraries;
     private String mDataDir;
@@ -116,14 +114,18 @@ public final class LoadedApk {
     private ClassLoader mClassLoader;
     private Application mApplication;
 
+    private String[] mSplitNames;
+    private String[] mSplitAppDirs;
+    private String[] mSplitResDirs;
+
     private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
-        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
-        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
-        = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
-        = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+        = new ArrayMap<>();
 
     int mClientCount = 0;
 
@@ -300,9 +302,18 @@ public final class LoadedApk {
         synchronized (this) {
             createOrUpdateClassLoaderLocked(addedPaths);
             if (mResources != null) {
-                mResources = mActivityThread.getTopLevelResources(mResDir, mSplitResDirs,
-                        mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
-                        this);
+                final String[] splitPaths;
+                try {
+                    splitPaths = getSplitPaths(null);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // This should NEVER fail.
+                    throw new AssertionError("null split not found");
+                }
+
+                mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+                        splitPaths, mOverlayDirs, mSharedLibraries,
+                        Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                        getClassLoader());
             }
         }
     }
@@ -313,8 +324,6 @@ public final class LoadedApk {
         mApplicationInfo = aInfo;
         mAppDir = aInfo.sourceDir;
         mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
-        mSplitAppDirs = aInfo.splitSourceDirs;
-        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
         mOverlayDirs = aInfo.resourceDirs;
         mSharedLibraries = aInfo.sharedLibraryFiles;
         mDataDir = aInfo.dataDir;
@@ -322,19 +331,28 @@ public final class LoadedApk {
         mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
         mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
         mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
+
+        mSplitNames = aInfo.splitNames;
+        mSplitAppDirs = aInfo.splitSourceDirs;
+        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
+
+        if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
+            mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+        }
     }
 
     public static void makePaths(ActivityThread activityThread, ApplicationInfo aInfo,
             List<String> outZipPaths, List<String> outLibPaths) {
         final String appDir = aInfo.sourceDir;
-        final String[] splitAppDirs = aInfo.splitSourceDirs;
         final String libDir = aInfo.nativeLibraryDir;
         final String[] sharedLibraries = aInfo.sharedLibraryFiles;
 
         outZipPaths.clear();
         outZipPaths.add(appDir);
-        if (splitAppDirs != null) {
-            Collections.addAll(outZipPaths, splitAppDirs);
+
+        // Do not load all available splits if the app requested isolated split loading.
+        if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) {
+            Collections.addAll(outZipPaths, aInfo.splitSourceDirs);
         }
 
         if (outLibPaths != null) {
@@ -367,13 +385,18 @@ public final class LoadedApk {
                     || appDir.equals(instrumentedAppDir)) {
                 outZipPaths.clear();
                 outZipPaths.add(instrumentationAppDir);
-                if (instrumentationSplitAppDirs != null) {
-                    Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
-                }
-                if (!instrumentationAppDir.equals(instrumentedAppDir)) {
-                    outZipPaths.add(instrumentedAppDir);
-                    if (instrumentedSplitAppDirs != null) {
-                        Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+
+                // Only add splits if the app did not request isolated split loading.
+                if (!aInfo.requestsIsolatedSplitLoading()) {
+                    if (instrumentationSplitAppDirs != null) {
+                        Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
+                    }
+
+                    if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+                        outZipPaths.add(instrumentedAppDir);
+                        if (instrumentedSplitAppDirs != null) {
+                            Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+                        }
                     }
                 }
 
@@ -399,7 +422,7 @@ public final class LoadedApk {
             // will be added to zipPaths that shouldn't be part of the library path.
             if (aInfo.primaryCpuAbi != null) {
                 // Add fake libs into the library search path if we target prior to N.
-                if (aInfo.targetSdkVersion <= 23) {
+                if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
                     outLibPaths.add("/system/fake-libs" +
                         (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
                 }
@@ -434,6 +457,116 @@ public final class LoadedApk {
         }
     }
 
+    private class SplitDependencyLoader
+            extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
+        private String[] mCachedBaseResourcePath;
+        private final String[][] mCachedResourcePaths;
+        private final ClassLoader[] mCachedSplitClassLoaders;
+
+        SplitDependencyLoader(SparseIntArray dependencies) {
+            super(dependencies);
+            mCachedResourcePaths = new String[mSplitNames.length][];
+            mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+        }
+
+        @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]);
+                }
+            }
+        }
+
+        @Override
+        protected void constructSplit(int splitIdx, int parentSplitIdx) throws
+                PackageManager.NameNotFoundException {
+            final ArrayList<String> splitPaths = new ArrayList<>();
+            if (splitIdx == -1) {
+                createOrUpdateClassLoaderLocked(null);
+                addAllConfigSplits(null, splitPaths);
+                mCachedBaseResourcePath = 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]);
+            }
+
+            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 {
+                idx = Arrays.binarySearch(mSplitNames, splitName);
+                if (idx < 0) {
+                    throw new PackageManager.NameNotFoundException(
+                            "Split name '" + splitName + "' is not installed");
+                }
+            }
+
+            loadDependenciesForSplit(idx);
+            return idx;
+        }
+
+        ClassLoader getClassLoaderForSplit(String splitName)
+                throws PackageManager.NameNotFoundException {
+            final int idx = ensureSplitLoaded(splitName);
+            if (idx < 0) {
+                return mClassLoader;
+            }
+            return mCachedSplitClassLoaders[idx];
+        }
+
+        String[] getSplitPathsForSplit(String splitName)
+                throws PackageManager.NameNotFoundException {
+            final int idx = ensureSplitLoaded(splitName);
+            if (idx < 0) {
+                return mCachedBaseResourcePath;
+            }
+            return mCachedResourcePaths[idx];
+        }
+    }
+
+    private SplitDependencyLoader mSplitLoader;
+
+    ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+        if (mSplitLoader == null) {
+            return mClassLoader;
+        }
+        return mSplitLoader.getClassLoaderForSplit(splitName);
+    }
+
+    String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+        if (mSplitLoader == null) {
+            return mSplitResDirs;
+        }
+        return mSplitLoader.getSplitPathsForSplit(splitName);
+    }
+
     private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
         if (mPackageName.equals("android")) {
             // Note: This branch is taken for system server and we don't need to setup
@@ -790,6 +923,10 @@ public final class LoadedApk {
         return mOverlayDirs;
     }
 
+    public String[] getSharedLibraries() {
+        return mSharedLibraries;
+    }
+
     public String getDataDir() {
         return mDataDir;
     }
@@ -806,14 +943,24 @@ public final class LoadedApk {
         return mCredentialProtectedDataDirFile;
     }
 
-    public AssetManager getAssets(ActivityThread mainThread) {
-        return getResources(mainThread).getAssets();
+    public AssetManager getAssets() {
+        return getResources().getAssets();
     }
 
-    public Resources getResources(ActivityThread mainThread) {
+    public Resources getResources() {
         if (mResources == null) {
-            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
-                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
+            final String[] splitPaths;
+            try {
+                splitPaths = getSplitPaths(null);
+            } catch (PackageManager.NameNotFoundException e) {
+                // This should never fail.
+                throw new AssertionError("null split not found");
+            }
+
+            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+                    splitPaths, mOverlayDirs, mSharedLibraries,
+                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                    getClassLoader());
         }
         return mResources;
     }
@@ -870,8 +1017,7 @@ public final class LoadedApk {
         }
 
         // Rewrite the R 'constants' for all library apks.
-        SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
-                .getAssignedPackageIdentifiers();
+        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
         final int N = packageIdentifiers.size();
         for (int i = 0; i < N; i++) {
             final int id = packageIdentifiers.keyAt(i);
index 38e6fbe..f00f605 100644 (file)
@@ -4253,6 +4253,20 @@ public abstract class Context {
             int flags) throws PackageManager.NameNotFoundException;
 
     /**
+     * Return a new Context object for the given split name. The new Context has a ClassLoader and
+     * Resources object that can access the split's and all of its dependencies' code/resources.
+     * Each call to this method returns a new instance of a Context object;
+     * Context objects are not shared, however common state (ClassLoader, other Resources for
+     * the same split) may be so the Context itself can be fairly lightweight.
+     *
+     * @param splitName The name of the split to include, as declared in the split's
+     *                  <code>AndroidManifest.xml</code>.
+     * @return A {@link Context} with the given split's code and/or resources loaded.
+     */
+    public abstract Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException;
+
+    /**
      * Get the userId associated with this context
      * @return user id
      *
index b131ecc..546bfc4 100644 (file)
@@ -16,7 +16,6 @@
 
 package android.content;
 
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -826,6 +825,13 @@ public class ContextWrapper extends Context {
 
     /** @hide */
     @Override
+    public Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException {
+        return mBase.createContextForSplit(splitName);
+    }
+
+    /** @hide */
+    @Override
     public int getUserId() {
         return mBase.getUserId();
     }
index 04ab239..3d9ba96 100644 (file)
@@ -31,6 +31,7 @@ import android.os.Parcelable;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -558,6 +559,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     public static final int PRIVATE_FLAG_STATIC_SHARED_LIBRARY = 1 << 13;
 
     /**
+     * Value for {@linl #privateFlags}: When set, the application will only have its splits loaded
+     * if they are required to load a component. Splits can be loaded on demand using the
+     * {@link Context#createContextForSplit(String)} API.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ISOLATED_SPLIT_LOADING = 1 << 14;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -607,8 +616,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     public String publicSourceDir;
 
     /**
-     * Full paths to zero or more split APKs that, when combined with the base
-     * APK defined in {@link #sourceDir}, form a complete application.
+     * The names of all installed split APKs, ordered lexicographically.
+     */
+    public String[] splitNames;
+
+    /**
+     * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
      */
     public String[] splitSourceDirs;
 
@@ -616,14 +629,35 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
      * Full path to the publicly available parts of {@link #splitSourceDirs},
      * including resources and manifest. This may be different from
      * {@link #splitSourceDirs} if an application is forward locked.
+     *
+     * @see #splitSourceDirs
      */
     public String[] splitPublicSourceDirs;
 
     /**
-     * Full paths to the locations of extra resource packages this application
-     * uses. This field is only used if there are extra resource packages,
-     * otherwise it is null.
-     * 
+     * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+     *
+     * Available since platform version O.
+     *
+     * Only populated if the application opts in to isolated split loading via the
+     * {@link android.R.attr.isolatedSplits} attribute in the &lt;manifest&gt; tag of the app's
+     * AndroidManifest.xml.
+     *
+     * 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.
+     * 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;
+
+    /**
+     * Full paths to the locations of extra resource packages (runtime overlays)
+     * this application uses. This field is only used if there are extra resource
+     * packages, otherwise it is null.
+     *
      * {@hide}
      */
     public String[] resourceDirs;
@@ -1058,8 +1092,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         scanPublicSourceDir = orig.scanPublicSourceDir;
         sourceDir = orig.sourceDir;
         publicSourceDir = orig.publicSourceDir;
+        splitNames = orig.splitNames;
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
+        splitDependencies = orig.splitDependencies;
         nativeLibraryDir = orig.nativeLibraryDir;
         secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
         nativeLibraryRootDir = orig.nativeLibraryRootDir;
@@ -1098,6 +1134,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         return 0;
     }
 
+    @SuppressWarnings("unchecked")
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
         dest.writeString(taskAffinity);
@@ -1115,8 +1152,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         dest.writeString(scanPublicSourceDir);
         dest.writeString(sourceDir);
         dest.writeString(publicSourceDir);
+        dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
+        dest.writeSparseIntArray(splitDependencies);
         dest.writeString(nativeLibraryDir);
         dest.writeString(secondaryNativeLibraryDir);
         dest.writeString(nativeLibraryRootDir);
@@ -1155,6 +1194,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         }
     };
 
+    @SuppressWarnings("unchecked")
     private ApplicationInfo(Parcel source) {
         super(source);
         taskAffinity = source.readString();
@@ -1172,8 +1212,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
         scanPublicSourceDir = source.readString();
         sourceDir = source.readString();
         publicSourceDir = source.readString();
+        splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
+        splitDependencies = source.readSparseIntArray();
         nativeLibraryDir = source.readString();
         secondaryNativeLibraryDir = source.readString();
         nativeLibraryRootDir = source.readString();
@@ -1363,6 +1405,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     }
 
     /**
+     * Returns true if the app has declared in its manifest that it wants its split APKs to be
+     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
+     * @hide
+     */
+    public boolean requestsIsolatedSplitLoading() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
+    }
+
+    /**
      * @hide
      */
     public boolean isStaticSharedLibrary() {
index b091d7e..53be953 100644 (file)
@@ -45,6 +45,12 @@ public class ComponentInfo extends PackageItemInfo {
     public String processName;
 
     /**
+     * The name of the split in which this component is declared.
+     * Null if the component was declared in the base APK.
+     */
+    public String splitName;
+
+    /**
      * A string resource identifier (in the package's resources) containing
      * a user-readable description of the component.  From the "description"
      * attribute or, if not set, 0.
@@ -53,7 +59,7 @@ public class ComponentInfo extends PackageItemInfo {
     
     /**
      * Indicates whether or not this component may be instantiated.  Note that this value can be
-     * overriden by the one in its parent {@link ApplicationInfo}.
+     * overridden by the one in its parent {@link ApplicationInfo}.
      */
     public boolean enabled = true;
 
@@ -72,11 +78,6 @@ public class ComponentInfo extends PackageItemInfo {
      */
     public boolean directBootAware = false;
 
-    /**
-     * The name of the split that contains the code for this component.
-     */
-    public String splitName;
-
     /** @removed */
     @Deprecated
     public boolean encryptionAware = false;
@@ -88,6 +89,7 @@ public class ComponentInfo extends PackageItemInfo {
         super(orig);
         applicationInfo = orig.applicationInfo;
         processName = orig.processName;
+        splitName = orig.splitName;
         descriptionRes = orig.descriptionRes;
         enabled = orig.enabled;
         exported = orig.exported;
@@ -168,6 +170,9 @@ public class ComponentInfo extends PackageItemInfo {
         if (processName != null && !packageName.equals(processName)) {
             pw.println(prefix + "processName=" + processName);
         }
+        if (splitName != null) {
+            pw.println(prefix + "splitName=" + splitName);
+        }
         pw.println(prefix + "enabled=" + enabled + " exported=" + exported
                 + " directBootAware=" + directBootAware);
         if (descriptionRes != 0) {
@@ -200,6 +205,7 @@ public class ComponentInfo extends PackageItemInfo {
             applicationInfo.writeToParcel(dest, parcelableFlags);
         }
         dest.writeString(processName);
+        dest.writeString(splitName);
         dest.writeInt(descriptionRes);
         dest.writeInt(enabled ? 1 : 0);
         dest.writeInt(exported ? 1 : 0);
@@ -213,6 +219,7 @@ public class ComponentInfo extends PackageItemInfo {
             applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
         }
         processName = source.readString();
+        splitName = source.readString();
         descriptionRes = source.readInt();
         enabled = (source.readInt() != 0);
         exported = (source.readInt() != 0);
index 9d88cdd..a135d8f 100644 (file)
@@ -18,6 +18,8 @@ package android.content.pm;
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 /**
  * Information you can retrieve about a particular piece of test
@@ -44,8 +46,12 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
     public String publicSourceDir;
 
     /**
-     * Full paths to zero or more split APKs that, when combined with the base
-     * APK defined in {@link #sourceDir}, form a complete application.
+     * The names of all installed split APKs, ordered lexicographically.
+     */
+    public String[] splitNames;
+
+    /**
+     * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
      */
     public String[] splitSourceDirs;
 
@@ -53,10 +59,31 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
      * Full path to the publicly available parts of {@link #splitSourceDirs},
      * including resources and manifest. This may be different from
      * {@link #splitSourceDirs} if an application is forward locked.
+     *
+     * @see #splitSourceDirs
      */
     public String[] splitPublicSourceDirs;
 
     /**
+     * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+     *
+     * Available since platform version O.
+     *
+     * Only populated if the application opts in to isolated split loading via the
+     * {@link android.R.attr.isolatedSplits} attribute in the &lt;manifest&gt; tag of the app's
+     * AndroidManifest.xml.
+     *
+     * 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.
+     * 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;
+
+    /**
      * Full path to a directory assigned to the package for its persistent data.
      */
     public String dataDir;
@@ -88,8 +115,10 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
         targetPackage = orig.targetPackage;
         sourceDir = orig.sourceDir;
         publicSourceDir = orig.publicSourceDir;
+        splitNames = orig.splitNames;
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
+        splitDependencies = orig.splitDependencies;
         dataDir = orig.dataDir;
         deviceProtectedDataDir = orig.deviceProtectedDataDir;
         credentialProtectedDataDir = orig.credentialProtectedDataDir;
@@ -114,8 +143,10 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
         dest.writeString(targetPackage);
         dest.writeString(sourceDir);
         dest.writeString(publicSourceDir);
+        dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
+        dest.writeSparseIntArray(splitDependencies);
         dest.writeString(dataDir);
         dest.writeString(deviceProtectedDataDir);
         dest.writeString(credentialProtectedDataDir);
@@ -135,13 +166,16 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
         }
     };
 
+    @SuppressWarnings("unchecked")
     private InstrumentationInfo(Parcel source) {
         super(source);
         targetPackage = source.readString();
         sourceDir = source.readString();
         publicSourceDir = source.readString();
+        splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
+        splitDependencies = source.readSparseIntArray();
         dataDir = source.readString();
         deviceProtectedDataDir = source.readString();
         credentialProtectedDataDir = source.readString();
@@ -156,8 +190,10 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
         ai.packageName = packageName;
         ai.sourceDir = sourceDir;
         ai.publicSourceDir = publicSourceDir;
+        ai.splitNames = splitNames;
         ai.splitSourceDirs = splitSourceDirs;
         ai.splitPublicSourceDirs = splitPublicSourceDirs;
+        ai.splitDependencies = splitDependencies;
         ai.dataDir = dataDir;
         ai.deviceProtectedDataDir = deviceProtectedDataDir;
         ai.credentialProtectedDataDir = credentialProtectedDataDir;
index cd51bce..8223726 100644 (file)
@@ -49,6 +49,9 @@ import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.split.SplitAssetDependencyLoader;
+import android.content.pm.split.SplitAssetLoader;
+import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -74,6 +77,7 @@ import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.jar.StrictJarFile;
@@ -106,6 +110,7 @@ import java.security.spec.InvalidKeySpecException;
 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;
@@ -155,6 +160,7 @@ public class PackageParser {
 
     private static final String TAG_MANIFEST = "manifest";
     private static final String TAG_APPLICATION = "application";
+    private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_OVERLAY = "overlay";
     private static final String TAG_KEY_SETS = "key-sets";
     private static final String TAG_PERMISSION_GROUP = "permission-group";
@@ -178,6 +184,7 @@ public class PackageParser {
     private static final String TAG_EAT_COMMENT = "eat-comment";
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_RESTRICT_UPDATE = "restrict-update";
+    private static final String TAG_USES_SPLIT = "uses-split";
 
     /**
      * Bit mask of all the valid bits that can be set in restartOnConfigChanges.
@@ -352,6 +359,9 @@ public class PackageParser {
         /** Names of any split APKs, ordered by parsed splitName */
         public final String[] splitNames;
 
+        /** Dependencies of any split APKs, ordered by parsed splitName */
+        public final String[] usesSplitNames;
+
         /**
          * Path where this package was found on disk. For monolithic packages
          * this is path to single base APK file; for cluster packages this is
@@ -374,14 +384,17 @@ public class PackageParser {
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
+        public final boolean isolatedSplits;
 
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
-                String[] splitCodePaths, int[] splitRevisionCodes) {
+                String[] usesSplitNames, String[] splitCodePaths,
+                int[] splitRevisionCodes) {
             this.packageName = baseApk.packageName;
             this.versionCode = baseApk.versionCode;
             this.installLocation = baseApk.installLocation;
             this.verifiers = baseApk.verifiers;
             this.splitNames = splitNames;
+            this.usesSplitNames = usesSplitNames;
             this.codePath = codePath;
             this.baseCodePath = baseApk.codePath;
             this.splitCodePaths = splitCodePaths;
@@ -392,6 +405,7 @@ public class PackageParser {
             this.multiArch = baseApk.multiArch;
             this.use32bitAbi = baseApk.use32bitAbi;
             this.extractNativeLibs = baseApk.extractNativeLibs;
+            this.isolatedSplits = baseApk.isolatedSplits;
         }
 
         public List<String> getAllCodePaths() {
@@ -411,6 +425,7 @@ public class PackageParser {
         public final String codePath;
         public final String packageName;
         public final String splitName;
+        public final String usesSplitName;
         public final int versionCode;
         public final int revisionCode;
         public final int installLocation;
@@ -422,15 +437,17 @@ public class PackageParser {
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
+        public final boolean isolatedSplits;
 
-        public ApkLite(String codePath, String packageName, String splitName, int versionCode,
-                int revisionCode, int installLocation, List<VerifierInfo> verifiers,
+        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 extractNativeLibs, boolean isolatedSplits) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
+            this.usesSplitName = usesSplitName;
             this.versionCode = versionCode;
             this.revisionCode = revisionCode;
             this.installLocation = installLocation;
@@ -442,6 +459,7 @@ public class PackageParser {
             this.multiArch = multiArch;
             this.use32bitAbi = use32bitAbi;
             this.extractNativeLibs = extractNativeLibs;
+            this.isolatedSplits = isolatedSplits;
         }
     }
 
@@ -492,7 +510,7 @@ public class PackageParser {
         return isApkPath(file.getName());
     }
 
-    private static boolean isApkPath(String path) {
+    public static boolean isApkPath(String path) {
         return path.endsWith(".apk");
     }
 
@@ -738,23 +756,23 @@ public class PackageParser {
     public static PackageLite parsePackageLite(File packageFile, int flags)
             throws PackageParserException {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(packageFile, flags, null);
+            return parseClusterPackageLite(packageFile, flags);
         } else {
-            return parseMonolithicPackageLite(packageFile, flags, null);
+            return parseMonolithicPackageLite(packageFile, flags);
         }
     }
 
-    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags,
-            AssetManager cachedAssetManager) throws PackageParserException {
+    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
+            throws PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
-        final ApkLite baseApk = parseApkLite(packageFile, flags, cachedAssetManager);
+        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);
+        return new PackageLite(packagePath, baseApk, null, null, null, null);
     }
 
-    private static PackageLite parseClusterPackageLite(File packageDir, int flags,
-            AssetManager cachedAssetManager) throws PackageParserException {
+    private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+            throws PackageParserException {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -768,7 +786,7 @@ public class PackageParser {
         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         for (File file : files) {
             if (isApkFile(file)) {
-                final ApkLite lite = parseApkLite(file, flags, cachedAssetManager);
+                final ApkLite lite = parseApkLite(file, flags);
 
                 // Assert that all package names and version codes are
                 // consistent with the first one we encounter.
@@ -808,10 +826,12 @@ public class PackageParser {
         final int size = apks.size();
 
         String[] splitNames = null;
+        String[] usesSplitNames = null;
         String[] splitCodePaths = null;
         int[] splitRevisionCodes = null;
         if (size > 0) {
             splitNames = new String[size];
+            usesSplitNames = new String[size];
             splitCodePaths = new String[size];
             splitRevisionCodes = new int[size];
 
@@ -819,13 +839,15 @@ public class PackageParser {
             Arrays.sort(splitNames, sSplitNameComparator);
 
             for (int i = 0; i < size; i++) {
-                splitCodePaths[i] = apks.get(splitNames[i]).codePath;
-                splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode;
+                final ApkLite apk = apks.get(splitNames[i]);
+                usesSplitNames[i] = apk.usesSplitName;
+                splitCodePaths[i] = apk.codePath;
+                splitRevisionCodes[i] = apk.revisionCode;
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
-        return new PackageLite(codePath, baseApk, splitNames, splitCodePaths,
+        return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
                 splitRevisionCodes);
     }
 
@@ -1004,6 +1026,42 @@ public class PackageParser {
         }
     }
 
+    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
@@ -1014,25 +1072,24 @@ public class PackageParser {
      * must be done separately in {@link #collectCertificates(Package, int)}.
      */
     private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
-        final AssetManager assets = newConfiguredAssetManager();
-        final PackageLite lite = parseClusterPackageLite(packageDir, 0, assets);
-
+        final PackageLite lite = parseClusterPackageLite(packageDir, 0);
         if (mOnlyCoreApps && !lite.coreApp) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "Not a coreApp: " + packageDir);
         }
 
-        try {
-            // Load all splits into the AssetManager (base has already been loaded earlier)
-            // so that resources can be overriden when parsing the manifests.
-            loadApkIntoAssetManager(assets, lite.baseCodePath, flags);
-
-            if (!ArrayUtils.isEmpty(lite.splitCodePaths)) {
-                for (String path : lite.splitCodePaths) {
-                    loadApkIntoAssetManager(assets, path, flags);
-                }
-            }
+        // Build the split dependency tree.
+        SparseIntArray splitDependencies = null;
+        final SplitAssetLoader assetLoader;
+        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+            splitDependencies = buildSplitDependencyTree(lite);
+            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+        } else {
+            assetLoader = new DefaultSplitAssetLoader(lite, flags);
+        }
 
+        try {
+            final AssetManager assets = assetLoader.getBaseAssetManager();
             final File baseApk = new File(lite.baseCodePath);
             final Package pkg = parseBaseApk(baseApk, assets, flags);
             if (pkg == null) {
@@ -1047,9 +1104,12 @@ public class PackageParser {
                 pkg.splitRevisionCodes = lite.splitRevisionCodes;
                 pkg.splitFlags = new int[num];
                 pkg.splitPrivateFlags = new int[num];
+                pkg.applicationInfo.splitNames = pkg.splitNames;
+                pkg.applicationInfo.splitDependencies = splitDependencies;
 
                 for (int i = 0; i < num; i++) {
-                    parseSplitApk(pkg, i, assets, flags);
+                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+                    parseSplitApk(pkg, i, splitAssets, flags);
                 }
             }
 
@@ -1057,7 +1117,7 @@ public class PackageParser {
             pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
         } finally {
-            IoUtils.closeQuietly(assets);
+            IoUtils.closeQuietly(assetLoader);
         }
     }
 
@@ -1074,7 +1134,7 @@ public class PackageParser {
     @Deprecated
     public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
         final AssetManager assets = newConfiguredAssetManager();
-        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags, assets);
+        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
         if (mOnlyCoreApps) {
             if (!lite.coreApp) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -1168,7 +1228,7 @@ public class PackageParser {
 
         final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
 
-        Resources res = null;
+        final Resources res;
         XmlResourceParser parser = null;
         try {
             res = new Resources(assets, mMetrics, null);
@@ -1225,7 +1285,7 @@ public class PackageParser {
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("application")) {
+            if (tagName.equals(TAG_APPLICATION)) {
                 if (foundApp) {
                     if (RIGID_PARSER) {
                         outError[0] = "<manifest> has more than one <application>";
@@ -1523,17 +1583,12 @@ public class PackageParser {
      */
     public static ApkLite parseApkLite(File apkFile, int flags)
             throws PackageParserException {
-        return parseApkLite(apkFile, flags, null);
-    }
-
-    private static ApkLite parseApkLite(File apkFile, int flags,
-            @Nullable AssetManager cachedAssetManager) throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
         AssetManager assets = null;
         XmlResourceParser parser = null;
         try {
-            assets = cachedAssetManager == null ? newConfiguredAssetManager() : cachedAssetManager;
+            assets = newConfiguredAssetManager();
             int cookie = assets.addAssetPath(apkPath);
             if (cookie == 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -1543,7 +1598,6 @@ public class PackageParser {
             final DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
 
-            final Resources res = new Resources(assets, metrics, null);
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
             final Signature[] signatures;
@@ -1565,16 +1619,14 @@ public class PackageParser {
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);
+            return parseApkLite(apkPath, parser, attrs, flags, signatures, certificates);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            if (cachedAssetManager == null) {
-                IoUtils.closeQuietly(assets);
-            }
+            IoUtils.closeQuietly(assets);
         }
     }
 
@@ -1652,9 +1704,9 @@ public class PackageParser {
                 (splitName != null) ? splitName.intern() : splitName);
     }
 
-    private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates)
-                    throws IOException, XmlPullParserException, PackageParserException {
+    private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
+            int flags, Signature[] signatures, Certificate[][] certificates)
+            throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
         int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -1665,6 +1717,8 @@ public class PackageParser {
         boolean multiArch = false;
         boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
+        boolean isolatedSplits = false;
+        String usesSplitName = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             final String attr = attrs.getAttributeName(i);
@@ -1677,6 +1731,8 @@ public class PackageParser {
                 revisionCode = attrs.getAttributeIntValue(i, 0);
             } else if (attr.equals("coreApp")) {
                 coreApp = attrs.getAttributeBooleanValue(i, false);
+            } else if (attr.equals("isolatedSplits")) {
+                isolatedSplits = attrs.getAttributeBooleanValue(i, false);
             }
         }
 
@@ -1691,14 +1747,16 @@ public class PackageParser {
                 continue;
             }
 
-            if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
-                final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags);
+            if (parser.getDepth() != searchDepth) {
+                continue;
+            }
+
+            if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+                final VerifierInfo verifier = parseVerifier(attrs);
                 if (verifier != null) {
                     verifiers.add(verifier);
                 }
-            }
-
-            if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+            } else if (TAG_APPLICATION.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("debuggable".equals(attr)) {
@@ -1714,12 +1772,25 @@ public class PackageParser {
                         extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
                     }
                 }
+            } else if (TAG_USES_SPLIT.equals(parser.getName())) {
+                if (usesSplitName != null) {
+                    Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+                    continue;
+                }
+
+                usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name");
+                if (usesSplitName == null) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                            "<uses-split> tag requires 'android:name' attribute");
+                }
             }
         }
 
-        return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
-                revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
-                debuggable, multiArch, use32bitAbi, extractNativeLibs);
+        return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
+                versionCode, revisionCode, installLocation, verifiers, signatures,
+                certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
+                isolatedSplits);
     }
 
     /**
@@ -1940,6 +2011,10 @@ public class PackageParser {
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
         }
 
+        if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) {
+            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+        }
+
         // Resource boolean are -1, so 1 means we don't know the value.
         int supportsSmallScreens = 1;
         int supportsNormalScreens = 1;
@@ -3657,6 +3732,8 @@ public class PackageParser {
                 continue;
             }
 
+            ComponentInfo parsedComponent = null;
+
             String tagName = parser.getName();
             if (tagName.equals("activity")) {
                 Activity a = parseActivity(owner, res, parser, flags, outError, false,
@@ -3667,6 +3744,7 @@ public class PackageParser {
                 }
 
                 owner.activities.add(a);
+                parsedComponent = a.info;
 
             } else if (tagName.equals("receiver")) {
                 Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
@@ -3676,6 +3754,7 @@ public class PackageParser {
                 }
 
                 owner.receivers.add(a);
+                parsedComponent = a.info;
 
             } else if (tagName.equals("service")) {
                 Service s = parseService(owner, res, parser, flags, outError);
@@ -3685,6 +3764,7 @@ public class PackageParser {
                 }
 
                 owner.services.add(s);
+                parsedComponent = s.info;
 
             } else if (tagName.equals("provider")) {
                 Provider p = parseProvider(owner, res, parser, flags, outError);
@@ -3694,6 +3774,7 @@ public class PackageParser {
                 }
 
                 owner.providers.add(p);
+                parsedComponent = p.info;
 
             } else if (tagName.equals("activity-alias")) {
                 Activity a = parseActivityAlias(owner, res, parser, flags, outError);
@@ -3703,6 +3784,7 @@ public class PackageParser {
                 }
 
                 owner.activities.add(a);
+                parsedComponent = a.info;
 
             } else if (parser.getName().equals("meta-data")) {
                 // note: application meta-data is stored off to the side, so it can
@@ -3769,6 +3851,14 @@ public class PackageParser {
                     return false;
                 }
             }
+
+            if (parsedComponent != null && parsedComponent.splitName == null) {
+                // If the loaded component did not specify a split, inherit the split name
+                // based on the split it is defined in.
+                // This is used to later load the correct split when starting this
+                // component.
+                parsedComponent.splitName = owner.splitNames[splitIndex];
+            }
         }
 
         return true;
@@ -5039,18 +5129,23 @@ public class PackageParser {
         return data;
     }
 
-    private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags) {
-        final TypedArray sa = res.obtainAttributes(attrs,
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier);
-
-        final String packageName = sa.getNonResourceString(
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+    private static VerifierInfo parseVerifier(AttributeSet attrs) {
+        String packageName = null;
+        String encodedPublicKey = null;
 
-        final String encodedPublicKey = sa.getNonResourceString(
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
+        final int attrCount = attrs.getAttributeCount();
+        for (int i = 0; i < attrCount; i++) {
+            final int attrResId = attrs.getAttributeNameResource(i);
+            switch (attrResId) {
+                case com.android.internal.R.attr.name:
+                    packageName = attrs.getAttributeValue(i);
+                    break;
 
-        sa.recycle();
+                case com.android.internal.R.attr.publicKey:
+                    encodedPublicKey = attrs.getAttributeValue(i);
+                    break;
+            }
+        }
 
         if (packageName == null || packageName.length() == 0) {
             Slog.i(TAG, "verifier package name was null; skipping");
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644 (file)
index 0000000..5a9966d
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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 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.res.AssetManager;
+import android.os.Build;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+/**
+ * Loads the base and split APKs into a single AssetManager.
+ * @hide
+ */
+public class DefaultSplitAssetLoader implements SplitAssetLoader {
+    private final String mBaseCodePath;
+    private final String[] mSplitCodePaths;
+    private final int mFlags;
+
+    private AssetManager mCachedAssetManager;
+
+    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+        mBaseCodePath = pkg.baseCodePath;
+        mSplitCodePaths = pkg.splitCodePaths;
+        mFlags = flags;
+    }
+
+    private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
+            throws PackageParser.PackageParserException {
+        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
+            throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + apkPath);
+        }
+
+        if (assets.addAssetPath(apkPath) == 0) {
+            throw new PackageParser.PackageParserException(
+                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                    "Failed adding asset path: " + apkPath);
+        }
+    }
+
+    @Override
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+        if (mCachedAssetManager != null) {
+            return mCachedAssetManager;
+        }
+
+        AssetManager assets = new AssetManager();
+        try {
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+
+            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+                for (String apkPath : mSplitCodePaths) {
+                    loadApkIntoAssetManager(assets, apkPath, mFlags);
+                }
+            }
+
+            mCachedAssetManager = assets;
+            assets = null;
+            return mCachedAssetManager;
+        } finally {
+            if (assets != null) {
+                IoUtils.closeQuietly(assets);
+            }
+        }
+    }
+
+    @Override
+    public AssetManager getSplitAssetManager(int splitIdx)
+            throws PackageParser.PackageParserException {
+        return getBaseAssetManager();
+    }
+
+    @Override
+    public void close() throws Exception {
+        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
new file mode 100644 (file)
index 0000000..3ad45b6
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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 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.res.AssetManager;
+import android.os.Build;
+import android.util.SparseIntArray;
+
+import libcore.io.IoUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+ * is to be used when an application opts-in to isolated split loading.
+ * @hide
+ */
+public class SplitAssetDependencyLoader
+        extends SplitDependencyLoaderHelper<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 AssetManager[] mCachedAssetManagers;
+
+    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
+            int flags) {
+        super(dependencies);
+        mBasePath = pkg.baseCodePath;
+        mSplitNames = pkg.splitNames;
+        mSplitPaths = pkg.splitCodePaths;
+        mFlags = flags;
+        mCachedBasePaths = null;
+        mCachedBaseAssetManager = null;
+        mCachedSplitPaths = new String[mSplitNames.length][];
+        mCachedAssetManagers = new AssetManager[mSplitNames.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]);
+            }
+        }
+    }
+
+    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
+            throws PackageParser.PackageParserException {
+        final AssetManager assets = new AssetManager();
+        try {
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    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, 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]);
+        }
+
+        assetPaths.add(mSplitPaths[splitIdx]);
+        addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
+        mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+                mFlags);
+    }
+
+    @Override
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+        loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
+        return mCachedBaseAssetManager;
+    }
+
+    @Override
+    public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
+        loadDependenciesForSplit(idx);
+        return mCachedAssetManagers[idx];
+    }
+
+    @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/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
new file mode 100644 (file)
index 0000000..108fb95
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.content.pm.PackageParser;
+import android.content.res.AssetManager;
+
+/**
+ * Simple interface for loading base Assets and Splits. Used by PackageParser when parsing
+ * split APKs.
+ *
+ * @hide
+ */
+public interface SplitAssetLoader extends AutoCloseable {
+    AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
+    AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
new file mode 100644 (file)
index 0000000..b493480
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 + "$");
+        }
+    }
+}
index d6d5cb6..1db685a 100644 (file)
@@ -26,6 +26,7 @@ import android.util.Size;
 import android.util.SizeF;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 
 import libcore.util.SneakyThrow;
 
@@ -891,6 +892,21 @@ public final class Parcel {
         }
     }
 
+    public final void writeSparseIntArray(SparseIntArray val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeInt(val.valueAt(i));
+            i++;
+        }
+    }
+
     public final void writeBooleanArray(boolean[] val) {
         if (val != null) {
             int N = val.length;
@@ -2154,6 +2170,20 @@ public final class Parcel {
     }
 
     /**
+     * Read and return a new SparseIntArray object from the parcel at the current
+     * dataPosition(). Returns null if the previously written array object was null.
+     */
+    public final SparseIntArray readSparseIntArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseIntArray sa = new SparseIntArray(N);
+        readSparseIntArrayInternal(sa, N);
+        return sa;
+    }
+
+    /**
      * Read and return a new ArrayList containing a particular object type from
      * the parcel that was written with {@link #writeTypedList} at the
      * current dataPosition().  Returns null if the
@@ -2922,6 +2952,15 @@ public final class Parcel {
         }
     }
 
+    private void readSparseIntArrayInternal(SparseIntArray outVal, int N) {
+        while (N > 0) {
+            int key = readInt();
+            int value = readInt();
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
     /**
      * @hide For testing
      */
index 113ace3..0dde91b 100644 (file)
         <enum name="preferExternal" value="2" />
     </attr>
 
+    <!-- If set to <code>true</code>, indicates to the platform that any split APKs
+         installed for this application should be loaded into their own Context
+         objects and not appear in the base application's Context.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="isolatedSplits" 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
         <attr name="sharedUserId" />
         <attr name="sharedUserLabel" />
         <attr name="installLocation" />
+        <attr name="isolatedSplits" />
     </declare-styleable>
 
     <!-- The <code>application</code> tag describes application-level components
         <attr name="hash" format="string" />
     </declare-styleable>
 
+    <declare-styleable name="AndroidManifestUsesSplit" parent="AndroidManifest">
+        <attr name="name" format="string" />
+    </declare-styleable>
+
 </resources>
index e387650..1146871 100644 (file)
         <public name="certDigest" />
         <public name="splitName" />
         <public name="colorMode" />
+        <public name="isolatedSplits" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
index d1aed3e..067a136 100644 (file)
@@ -894,7 +894,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
 
         // This is kind of hacky; we're creating a half-parsed package that is
         // straddled between the inherited and staged APKs.
-        final PackageLite pkg = new PackageLite(null, baseApk, null,
+        final PackageLite pkg = new PackageLite(null, baseApk, null, null,
                 splitPaths.toArray(new String[splitPaths.size()]), null);
         final boolean isForwardLocked =
                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
index 63a5d14..9899cd4 100644 (file)
@@ -10101,8 +10101,10 @@ public class PackageManagerService extends IPackageManager.Stub {
                 a.info.packageName = pkg.applicationInfo.packageName;
                 a.info.sourceDir = pkg.applicationInfo.sourceDir;
                 a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;
+                a.info.splitNames = pkg.splitNames;
                 a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs;
                 a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs;
+                a.info.splitDependencies = pkg.applicationInfo.splitDependencies;
                 a.info.dataDir = pkg.applicationInfo.dataDir;
                 a.info.deviceProtectedDataDir = pkg.applicationInfo.deviceProtectedDataDir;
                 a.info.credentialProtectedDataDir = pkg.applicationInfo.credentialProtectedDataDir;
index 49b96b0..2f8d749 100644 (file)
@@ -162,7 +162,7 @@ class PackageManagerShellCommand extends ShellCommand {
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
                             pkgLite, false, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
index b6e701e..9ffd92d 100644 (file)
@@ -692,6 +692,13 @@ public class MockContext extends Context {
         return null;
     }
 
+    /** @hide */
+    @Override
+    public Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
     /** {@hide} */
     @Override
     public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
index 68680d5..dff4f69 100644 (file)
@@ -1325,6 +1325,12 @@ public final class BridgeContext extends Context {
     }
 
     @Override
+    public Context createContextForSplit(String splitName) {
+        // pass
+        return null;
+    }
+
+    @Override
     public String[] databaseList() {
         // pass
         return null;