Add support for Split APK dependcies
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
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d814ddc..34eaa0b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2616,9 +2616,10 @@
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 @@
+ ", 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
try {
- Application app = packageInfo.makeApplication(false, mInstrumentation);
-
if (localLOGV) Slog.v(
TAG, "Performing receive of " + data.intent
+ ": app=" + app
@@ -3146,7 +3150,6 @@
+ ", 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 @@
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.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d37888d..9db2b92 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -32,6 +32,7 @@
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.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.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 @@
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
+ private ClassLoader mClassLoader;
private final IBinder mActivityToken;
@@ -272,8 +282,7 @@
@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 @@
pi.getResDir(),
pi.getSplitResDirs(),
pi.getOverlayDirs(),
- pi.getApplicationInfo().sharedLibraryFiles,
+ pi.getSharedLibraries(),
displayId,
overrideConfig,
compatInfo,
@@ -1901,7 +1910,8 @@
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 @@
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 @@
}
@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 @@
}
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 @@
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 @@
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 @@
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 @@
// 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 @@
mUser = user;
mPackageInfo = packageInfo;
+ mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
if (container != null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4ab0743..17f5edd 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -27,9 +27,11 @@
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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
|| 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 @@
// 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 @@
}
}
+ 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 @@
return mOverlayDirs;
}
+ public String[] getSharedLibraries() {
+ return mSharedLibraries;
+ }
+
public String getDataDir() {
return mDataDir;
}
@@ -806,14 +943,24 @@
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 @@
}
// 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);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 38e6fbe..f00f605 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4253,6 +4253,20 @@
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
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index b131ecc..546bfc4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -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 @@
/** @hide */
@Override
+ public Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createContextForSplit(splitName);
+ }
+
+ /** @hide */
+ @Override
public int getUserId() {
return mBase.getUserId();
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 04ab239..3d9ba96 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,6 +31,7 @@
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 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 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 @@
* 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 <manifest> 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 @@
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 @@
return 0;
}
+ @SuppressWarnings("unchecked")
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeString(taskAffinity);
@@ -1115,8 +1152,10 @@
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 @@
}
};
+ @SuppressWarnings("unchecked")
private ApplicationInfo(Parcel source) {
super(source);
taskAffinity = source.readString();
@@ -1172,8 +1212,10 @@
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 @@
}
/**
+ * 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() {
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index b091d7e..53be953 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -45,6 +45,12 @@
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 @@
/**
* 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 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 @@
super(orig);
applicationInfo = orig.applicationInfo;
processName = orig.processName;
+ splitName = orig.splitName;
descriptionRes = orig.descriptionRes;
enabled = orig.enabled;
exported = orig.exported;
@@ -168,6 +170,9 @@
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 @@
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 @@
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
}
processName = source.readString();
+ splitName = source.readString();
descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 9d88cdd..a135d8f 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -18,6 +18,8 @@
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 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 @@
* 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 <manifest> 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 @@
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 @@
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 @@
}
};
+ @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 @@
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;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index cd51bce..8223726 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -49,6 +49,9 @@
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.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.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 @@
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 @@
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 @@
/** 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 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 @@
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 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 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 @@
this.multiArch = multiArch;
this.use32bitAbi = use32bitAbi;
this.extractNativeLibs = extractNativeLibs;
+ this.isolatedSplits = isolatedSplits;
}
}
@@ -492,7 +510,7 @@
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 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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
* 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);
}
+ // 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 {
- // 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);
- }
- }
-
+ 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 @@
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 @@
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
- IoUtils.closeQuietly(assets);
+ IoUtils.closeQuietly(assetLoader);
}
}
@@ -1074,7 +1134,7 @@
@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 @@
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 @@
}
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 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 @@
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 @@
}
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 @@
(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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
owner.receivers.add(a);
+ parsedComponent = a.info;
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError);
@@ -3685,6 +3764,7 @@
}
owner.services.add(s);
+ parsedComponent = s.info;
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError);
@@ -3694,6 +3774,7 @@
}
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 @@
}
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 @@
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 @@
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);
+ private static VerifierInfo parseVerifier(AttributeSet attrs) {
+ String packageName = null;
+ String encodedPublicKey = null;
- final String packageName = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+ 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;
- final String encodedPublicKey = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
-
- 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
index 0000000..5a9966d
--- /dev/null
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -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
index 0000000..3ad45b6
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -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
index 0000000..108fb95
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -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
index 0000000..b493480
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
@@ -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 + "$");
+ }
+ }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d6d5cb6..1db685a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -26,6 +26,7 @@
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 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 @@
}
/**
+ * 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 @@
}
}
+ private void readSparseIntArrayInternal(SparseIntArray outVal, int N) {
+ while (N > 0) {
+ int key = readInt();
+ int value = readInt();
+ outVal.append(key, value);
+ N--;
+ }
+ }
+
/**
* @hide For testing
*/
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 113ace3..0dde91b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1001,6 +1001,13 @@
<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
@@ -1266,6 +1273,7 @@
<attr name="sharedUserId" />
<attr name="sharedUserLabel" />
<attr name="installLocation" />
+ <attr name="isolatedSplits" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
@@ -2462,4 +2470,8 @@
<attr name="hash" format="string" />
</declare-styleable>
+ <declare-styleable name="AndroidManifestUsesSplit" parent="AndroidManifest">
+ <attr name="name" format="string" />
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e387650..1146871 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2789,6 +2789,7 @@
<public name="certDigest" />
<public name="splitName" />
<public name="colorMode" />
+ <public name="isolatedSplits" />
</public-group>
<public-group type="style" first-id="0x010302e0">