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
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;
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;
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;
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;
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>);
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>);
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;
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
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);
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);
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;
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;
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>);
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>);
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);
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
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;
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;
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;
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;
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>);
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>);
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;
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));
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());
+ ", 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) {
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,
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) {
}
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
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) {
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);
}
try {
- Application app = packageInfo.makeApplication(false, mInstrumentation);
-
if (localLOGV) Slog.v(
TAG, "Performing receive of " + data.intent
+ ": app=" + app
+ ", comp=" + data.intent.getComponent().toShortString()
+ ", dir=" + packageInfo.getAppDir());
- ContextImpl context = (ContextImpl)app.getBaseContext();
sCurrentBroadcastIntent.set(data.intent);
receiver.setPendingResult(data);
receiver.onReceive(context.getReceiverRestrictedContext(),
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.
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;
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;
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 {
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
+ private ClassLoader mClassLoader;
private final IBinder mActivityToken;
@Override
public ClassLoader getClassLoader() {
- return mPackageInfo != null ?
- mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
+ return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}
@Override
pi.getResDir(),
pi.getSplitResDirs(),
pi.getOverlayDirs(),
- pi.getApplicationInfo().sharedLibraryFiles,
+ pi.getSharedLibraries(),
displayId,
overrideConfig,
compatInfo,
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;
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;
}
@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,
}
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,
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
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;
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;
// 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
mUser = user;
mPackageInfo = packageInfo;
+ mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
if (container != null) {
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;
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;
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);
private ApplicationInfo mApplicationInfo;
private String mAppDir;
private String mResDir;
- private String[] mSplitAppDirs;
- private String[] mSplitResDirs;
private String[] mOverlayDirs;
private String[] mSharedLibraries;
private String mDataDir;
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;
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());
}
}
}
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;
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) {
|| 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);
+ }
}
}
// 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" : ""));
}
}
}
+ 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
return mOverlayDirs;
}
+ public String[] getSharedLibraries() {
+ return mSharedLibraries;
+ }
+
public String getDataDir() {
return mDataDir;
}
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;
}
}
// 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);
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
*
package android.content;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
/** @hide */
@Override
+ public Context createContextForSplit(String splitName)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createContextForSplit(splitName);
+ }
+
+ /** @hide */
+ @Override
public int getUserId() {
return mBase.getUserId();
}
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Printer;
+import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
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}
*/
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;
* 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;
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;
return 0;
}
+ @SuppressWarnings("unchecked")
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeString(taskAffinity);
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);
}
};
+ @SuppressWarnings("unchecked")
private ApplicationInfo(Parcel source) {
super(source);
taskAffinity = source.readString();
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();
}
/**
+ * 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() {
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.
/**
* 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;
*/
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;
super(orig);
applicationInfo = orig.applicationInfo;
processName = orig.processName;
+ splitName = orig.splitName;
descriptionRes = orig.descriptionRes;
enabled = orig.enabled;
exported = orig.exported;
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) {
applicationInfo.writeToParcel(dest, parcelableFlags);
}
dest.writeString(processName);
+ dest.writeString(splitName);
dest.writeInt(descriptionRes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(exported ? 1 : 0);
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
}
processName = source.readString();
+ splitName = source.readString();
descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
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
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;
* 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;
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;
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);
}
};
+ @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();
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;
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;
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;
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;
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";
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.
/** 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
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;
this.multiArch = baseApk.multiArch;
this.use32bitAbi = baseApk.use32bitAbi;
this.extractNativeLibs = baseApk.extractNativeLibs;
+ this.isolatedSplits = baseApk.isolatedSplits;
}
public List<String> getAllCodePaths() {
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;
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;
this.multiArch = multiArch;
this.use32bitAbi = use32bitAbi;
this.extractNativeLibs = extractNativeLibs;
+ this.isolatedSplits = isolatedSplits;
}
}
return isApkPath(file.getName());
}
- private static boolean isApkPath(String path) {
+ public static boolean isApkPath(String path) {
return path.endsWith(".apk");
}
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,
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.
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];
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);
}
}
}
+ 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
* 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) {
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);
}
}
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
- IoUtils.closeQuietly(assets);
+ IoUtils.closeQuietly(assetLoader);
}
}
@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,
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
- Resources res = null;
+ final Resources res;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
}
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>";
*/
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,
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;
}
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);
}
}
(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;
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);
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);
}
}
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)) {
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);
}
/**
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;
continue;
}
+ ComponentInfo parsedComponent = null;
+
String tagName = parser.getName();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, flags, outError, false,
}
owner.activities.add(a);
+ parsedComponent = a.info;
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
}
owner.receivers.add(a);
+ parsedComponent = a.info;
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError);
}
owner.services.add(s);
+ parsedComponent = s.info;
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError);
}
owner.providers.add(p);
+ parsedComponent = p.info;
} else if (tagName.equals("activity-alias")) {
Activity a = parseActivityAlias(owner, res, parser, flags, outError);
}
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
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;
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");
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 + "$");
+ }
+ }
+}
import android.util.SizeF;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import libcore.util.SneakyThrow;
}
}
+ 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;
}
/**
+ * 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
}
}
+ private void readSparseIntArrayInternal(SparseIntArray outVal, int N) {
+ while (N > 0) {
+ int key = readInt();
+ int value = readInt();
+ outVal.append(key, value);
+ N--;
+ }
+ }
+
/**
* @hide For testing
*/
<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>
<public name="certDigest" />
<public name="splitName" />
<public name="colorMode" />
+ <public name="isolatedSplits" />
</public-group>
<public-group type="style" first-id="0x010302e0">
// 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;
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;
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) {
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)
}
@Override
+ public Context createContextForSplit(String splitName) {
+ // pass
+ return null;
+ }
+
+ @Override
public String[] databaseList() {
// pass
return null;