Merge changes from topic 'configForSplit'
* changes:
libandroidfw: Search all packages for an identifier
AAPT2: Finish support for feature splits
Add support for configForSplit
diff --git a/api/current.txt b/api/current.txt
index 24a1de9..aab29be 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -746,6 +746,7 @@
field public static final int isAsciiCapable = 16843753; // 0x10103e9
field public static final int isAuxiliary = 16843647; // 0x101037f
field public static final int isDefault = 16843297; // 0x1010221
+ field public static final int isFeatureSplit = 16844126; // 0x101055e
field public static final int isGame = 16843764; // 0x10103f4
field public static final int isIndicator = 16843079; // 0x1010147
field public static final int isModifier = 16843334; // 0x1010246
diff --git a/api/system-current.txt b/api/system-current.txt
index 3731fec..0f42a54 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -859,6 +859,7 @@
field public static final int isAsciiCapable = 16843753; // 0x10103e9
field public static final int isAuxiliary = 16843647; // 0x101037f
field public static final int isDefault = 16843297; // 0x1010221
+ field public static final int isFeatureSplit = 16844126; // 0x101055e
field public static final int isGame = 16843764; // 0x10103f4
field public static final int isIndicator = 16843079; // 0x1010147
field public static final int isModifier = 16843334; // 0x1010246
diff --git a/api/test-current.txt b/api/test-current.txt
index b665a03..f91bbb9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -746,6 +746,7 @@
field public static final int isAsciiCapable = 16843753; // 0x10103e9
field public static final int isAuxiliary = 16843647; // 0x101037f
field public static final int isDefault = 16843297; // 0x1010221
+ field public static final int isFeatureSplit = 16844126; // 0x101055e
field public static final int isGame = 16843764; // 0x10103f4
field public static final int isIndicator = 16843079; // 0x1010147
field public static final int isModifier = 16843334; // 0x1010246
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 91520f1..827a77f 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -411,7 +411,8 @@
if (file.isFile()) {
try {
ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+ null, null);
params.sessionParams.setSize(
PackageHelper.calculateInstalledSize(pkgLite, false,
params.sessionParams.abiOverride));
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ede9281..8a3d9b1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -156,13 +156,13 @@
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
- final ActivityThread mMainThread;
- final LoadedApk mPackageInfo;
- private ClassLoader mClassLoader;
+ final @NonNull ActivityThread mMainThread;
+ final @NonNull LoadedApk mPackageInfo;
+ private @Nullable ClassLoader mClassLoader;
- private final IBinder mActivityToken;
+ private final @Nullable IBinder mActivityToken;
- private final UserHandle mUser;
+ private final @Nullable UserHandle mUser;
private final ApplicationContentResolver mContentResolver;
@@ -181,6 +181,9 @@
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
+ // The name of the split this Context is representing. May be null.
+ private @Nullable String mSplitName = null;
+
private final Object mSync = new Object();
@GuardedBy("mSync")
@@ -1914,17 +1917,25 @@
}
}
- private static Resources createResources(IBinder activityToken, LoadedApk pi, int displayId,
- Configuration overrideConfig, CompatibilityInfo compatInfo) {
+ private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
+ int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
+ final String[] splitResDirs;
+ final ClassLoader classLoader;
+ try {
+ splitResDirs = pi.getSplitPaths(splitName);
+ classLoader = pi.getSplitClassLoader(splitName);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
- pi.getSplitResDirs(),
+ splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
- pi.getClassLoader());
+ classLoader);
}
@Override
@@ -1933,14 +1944,13 @@
LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- new UserHandle(UserHandle.getUserId(application.uid)), flags,
- null);
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
+ new UserHandle(UserHandle.getUserId(application.uid)), flags, null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- c.setResources(createResources(mActivityToken, pi, displayId, null,
+ c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
if (c.mResources != null) {
return c;
@@ -1964,20 +1974,20 @@
if (packageName.equals("system") || packageName.equals("android")) {
// The system resources are loaded in every application, so we can safely copy
// the context without reloading Resources.
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags,
- null);
+ return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,
+ flags, null);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags,
- null);
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
+ flags, null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- c.setResources(createResources(mActivityToken, pi, displayId, null,
+ c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
if (c.mResources != null) {
return c;
@@ -1999,7 +2009,7 @@
final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
final String[] paths = mPackageInfo.getSplitPaths(splitName);
- final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+ final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName,
mActivityToken, mUser, mFlags, classLoader);
final int displayId = mDisplay != null
@@ -2024,11 +2034,11 @@
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags, mClassLoader);
+ ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+ mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- context.setResources(createResources(mActivityToken, mPackageInfo, displayId,
+ context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
return context;
}
@@ -2039,12 +2049,12 @@
throw new IllegalArgumentException("display must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mFlags, mClassLoader);
+ ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+ mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = display.getDisplayId();
- context.setResources(createResources(mActivityToken, mPackageInfo, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo()));
+ context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
+ null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
context.mDisplay = display;
return context;
}
@@ -2053,16 +2063,16 @@
public Context createDeviceProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
- mClassLoader);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
+ flags, mClassLoader);
}
@Override
public Context createCredentialProtectedStorageContext() {
final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
| Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
- mClassLoader);
+ return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
+ flags, mClassLoader);
}
@Override
@@ -2149,7 +2159,7 @@
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
@@ -2159,7 +2169,7 @@
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
return context;
@@ -2186,8 +2196,8 @@
}
}
- ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
- 0, classLoader);
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
+ activityToken, null, 0, classLoader);
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2214,9 +2224,10 @@
return context;
}
- private ContextImpl(ContextImpl container, ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
- ClassLoader classLoader) {
+ private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
+ @NonNull LoadedApk packageInfo, @Nullable String splitName,
+ @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
+ @Nullable ClassLoader classLoader) {
mOuterContext = this;
// If creator didn't specify which storage to use, use the default
@@ -2241,6 +2252,7 @@
mUser = user;
mPackageInfo = packageInfo;
+ mSplitName = splitName;
mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index cf41e4e..dbed1be 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -27,7 +27,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.split.SplitDependencyLoaderHelper;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.split.SplitDependencyLoader;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Resources;
@@ -49,7 +50,6 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -304,7 +304,7 @@
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// This should NEVER fail.
throw new AssertionError("null split not found");
}
@@ -336,7 +336,7 @@
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
- mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+ mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
}
}
@@ -465,110 +465,88 @@
}
}
- private class SplitDependencyLoader
- extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
- private String[] mCachedBaseResourcePath;
+ /*
+ * All indices received by the super class should be shifted by 1 when accessing mSplitNames,
+ * etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't
+ * include the base APK in the list of splits.
+ */
+ private class SplitDependencyLoaderImpl extends SplitDependencyLoader<NameNotFoundException> {
private final String[][] mCachedResourcePaths;
- private final ClassLoader[] mCachedSplitClassLoaders;
+ private final ClassLoader[] mCachedClassLoaders;
- SplitDependencyLoader(SparseIntArray dependencies) {
+ SplitDependencyLoaderImpl(@NonNull SparseArray<int[]> dependencies) {
super(dependencies);
- mCachedResourcePaths = new String[mSplitNames.length][];
- mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+ mCachedResourcePaths = new String[mSplitNames.length + 1][];
+ mCachedClassLoaders = new ClassLoader[mSplitNames.length + 1];
}
@Override
protected boolean isSplitCached(int splitIdx) {
- if (splitIdx != -1) {
- return mCachedSplitClassLoaders[splitIdx] != null;
- }
- return mClassLoader != null && mCachedBaseResourcePath != null;
- }
-
- private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
- for (int i = 0; i < mSplitNames.length; i++) {
- if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
- outAssetPaths.add(mSplitResDirs[i]);
- }
- }
+ return mCachedClassLoaders[splitIdx] != null;
}
@Override
- protected void constructSplit(int splitIdx, int parentSplitIdx) throws
- PackageManager.NameNotFoundException {
+ protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+ int parentSplitIdx) throws NameNotFoundException {
final ArrayList<String> splitPaths = new ArrayList<>();
- if (splitIdx == -1) {
+ if (splitIdx == 0) {
createOrUpdateClassLoaderLocked(null);
- addAllConfigSplits(null, splitPaths);
- mCachedBaseResourcePath = splitPaths.toArray(new String[splitPaths.size()]);
+ mCachedClassLoaders[0] = mClassLoader;
+
+ // Never add the base resources here, they always get added no matter what.
+ for (int configSplitIdx : configSplitIndices) {
+ splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
+ }
+ mCachedResourcePaths[0] = splitPaths.toArray(new String[splitPaths.size()]);
return;
}
- final ClassLoader parent;
- if (parentSplitIdx == -1) {
- // The parent is the base APK, so use its ClassLoader as parent
- // and its configuration splits as part of our own too.
- parent = mClassLoader;
- Collections.addAll(splitPaths, mCachedBaseResourcePath);
- } else {
- parent = mCachedSplitClassLoaders[parentSplitIdx];
- Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+ // Since we handled the special base case above, parentSplitIdx is always valid.
+ final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
+ mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
+ mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent);
+
+ Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+ splitPaths.add(mSplitResDirs[splitIdx - 1]);
+ for (int configSplitIdx : configSplitIndices) {
+ splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
}
-
- mCachedSplitClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
- mSplitAppDirs[splitIdx], getTargetSdkVersion(), false, null, null, parent);
-
- splitPaths.add(mSplitResDirs[splitIdx]);
- addAllConfigSplits(mSplitNames[splitIdx], splitPaths);
mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
}
- private int ensureSplitLoaded(String splitName)
- throws PackageManager.NameNotFoundException {
- final int idx;
- if (splitName == null) {
- idx = -1;
- } else {
+ private int ensureSplitLoaded(String splitName) throws NameNotFoundException {
+ int idx = 0;
+ if (splitName != null) {
idx = Arrays.binarySearch(mSplitNames, splitName);
if (idx < 0) {
throw new PackageManager.NameNotFoundException(
"Split name '" + splitName + "' is not installed");
}
+ idx += 1;
}
-
loadDependenciesForSplit(idx);
return idx;
}
- ClassLoader getClassLoaderForSplit(String splitName)
- throws PackageManager.NameNotFoundException {
- final int idx = ensureSplitLoaded(splitName);
- if (idx < 0) {
- return mClassLoader;
- }
- return mCachedSplitClassLoaders[idx];
+ ClassLoader getClassLoaderForSplit(String splitName) throws NameNotFoundException {
+ return mCachedClassLoaders[ensureSplitLoaded(splitName)];
}
- String[] getSplitPathsForSplit(String splitName)
- throws PackageManager.NameNotFoundException {
- final int idx = ensureSplitLoaded(splitName);
- if (idx < 0) {
- return mCachedBaseResourcePath;
- }
- return mCachedResourcePaths[idx];
+ String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
+ return mCachedResourcePaths[ensureSplitLoaded(splitName)];
}
}
- private SplitDependencyLoader mSplitLoader;
+ private SplitDependencyLoaderImpl mSplitLoader;
- ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+ ClassLoader getSplitClassLoader(String splitName) throws NameNotFoundException {
if (mSplitLoader == null) {
return mClassLoader;
}
return mSplitLoader.getClassLoaderForSplit(splitName);
}
- String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+ String[] getSplitPaths(String splitName) throws NameNotFoundException {
if (mSplitLoader == null) {
return mSplitResDirs;
}
@@ -925,7 +903,7 @@
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// This should never fail.
throw new AssertionError("null split not found");
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b4d77a0..0b3742f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,7 +31,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Printer;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
@@ -656,13 +656,15 @@
*
* The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
* and {@link #splitPublicSourceDirs} arrays.
- * Each key represents a split and its value is its parent split.
+ * Each key represents a split and its value is an array of splits. The first element of this
+ * array is the parent split, and the rest are configuration splits. These configuration splits
+ * have no dependencies themselves.
* Cycles do not exist because they are illegal and screened for during installation.
*
* May be null if no splits are installed, or if no dependencies exist between them.
* @hide
*/
- public SparseIntArray splitDependencies;
+ public SparseArray<int[]> splitDependencies;
/**
* Full paths to the locations of extra resource packages (runtime overlays)
@@ -1182,7 +1184,7 @@
dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
- dest.writeSparseIntArray(splitDependencies);
+ dest.writeSparseArray((SparseArray) splitDependencies);
dest.writeString(nativeLibraryDir);
dest.writeString(secondaryNativeLibraryDir);
dest.writeString(nativeLibraryRootDir);
@@ -1244,7 +1246,7 @@
splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
- splitDependencies = source.readSparseIntArray();
+ splitDependencies = source.readSparseArray(null);
nativeLibraryDir = source.readString();
secondaryNativeLibraryDir = source.readString();
nativeLibraryRootDir = source.readString();
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 59c5307..f6f1be6 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -82,13 +82,15 @@
*
* The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
* and {@link #splitPublicSourceDirs} arrays.
- * Each key represents a split and its value is its parent split.
+ * Each key represents a split and its value is an array of splits. The first element of this
+ * array is the parent split, and the rest are configuration splits. These configuration splits
+ * have no dependencies themselves.
* Cycles do not exist because they are illegal and screened for during installation.
*
* May be null if no splits are installed, or if no dependencies exist between them.
* @hide
*/
- public SparseIntArray splitDependencies;
+ public SparseArray<int[]> splitDependencies;
/**
* Full path to a directory assigned to the package for its persistent data.
@@ -155,7 +157,7 @@
dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
- dest.writeSparseIntArray(splitDependencies);
+ dest.writeSparseArray((SparseArray) splitDependencies);
dest.writeString(dataDir);
dest.writeString(deviceProtectedDataDir);
dest.writeString(credentialProtectedDataDir);
@@ -185,7 +187,7 @@
splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
- splitDependencies = source.readSparseIntArray();
+ splitDependencies = source.readSparseArray(null);
dataDir = source.readString();
deviceProtectedDataDir = source.readString();
credentialProtectedDataDir = source.readString();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index be6dc05..99aa1bc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -76,7 +76,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.jar.StrictJarFile;
@@ -109,7 +109,6 @@
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -368,8 +367,12 @@
/** Names of any split APKs, ordered by parsed splitName */
public final String[] splitNames;
+ /** Names of any split APKs that are features. Ordered by splitName */
+ public final boolean[] isFeatureSplits;
+
/** Dependencies of any split APKs, ordered by parsed splitName */
public final String[] usesSplitNames;
+ public final String[] configForSplit;
/**
* Path where this package was found on disk. For monolithic packages
@@ -396,14 +399,16 @@
public final boolean isolatedSplits;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
- String[] usesSplitNames, String[] splitCodePaths,
- int[] splitRevisionCodes) {
+ boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit,
+ String[] splitCodePaths, int[] splitRevisionCodes) {
this.packageName = baseApk.packageName;
this.versionCode = baseApk.versionCode;
this.installLocation = baseApk.installLocation;
this.verifiers = baseApk.verifiers;
this.splitNames = splitNames;
+ this.isFeatureSplits = isFeatureSplits;
this.usesSplitNames = usesSplitNames;
+ this.configForSplit = configForSplit;
this.codePath = codePath;
this.baseCodePath = baseApk.codePath;
this.splitCodePaths = splitCodePaths;
@@ -434,6 +439,8 @@
public final String codePath;
public final String packageName;
public final String splitName;
+ public boolean isFeatureSplit;
+ public final String configForSplit;
public final String usesSplitName;
public final int versionCode;
public final int revisionCode;
@@ -448,14 +455,17 @@
public final boolean extractNativeLibs;
public final boolean isolatedSplits;
- public ApkLite(String codePath, String packageName, String splitName, String usesSplitName,
- int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, Certificate[][] certificates, boolean coreApp,
- boolean debuggable, boolean multiArch, boolean use32bitAbi,
- boolean extractNativeLibs, boolean isolatedSplits) {
+ public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+ String configForSplit, String usesSplitName, int versionCode, int revisionCode,
+ int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
+ Certificate[][] certificates, boolean coreApp, boolean debuggable,
+ boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs,
+ boolean isolatedSplits) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
+ this.isFeatureSplit = isFeatureSplit;
+ this.configForSplit = configForSplit;
this.usesSplitName = usesSplitName;
this.versionCode = versionCode;
this.revisionCode = revisionCode;
@@ -811,10 +821,10 @@
final ApkLite baseApk = parseApkLite(packageFile, flags);
final String packagePath = packageFile.getAbsolutePath();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- return new PackageLite(packagePath, baseApk, null, null, null, null);
+ return new PackageLite(packagePath, baseApk, null, null, null, null, null, null);
}
- private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+ static PackageLite parseClusterPackageLite(File packageDir, int flags)
throws PackageParserException {
final File[] files = packageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
@@ -869,12 +879,16 @@
final int size = apks.size();
String[] splitNames = null;
+ boolean[] isFeatureSplits = null;
String[] usesSplitNames = null;
+ String[] configForSplits = null;
String[] splitCodePaths = null;
int[] splitRevisionCodes = null;
if (size > 0) {
splitNames = new String[size];
+ isFeatureSplits = new boolean[size];
usesSplitNames = new String[size];
+ configForSplits = new String[size];
splitCodePaths = new String[size];
splitRevisionCodes = new int[size];
@@ -884,14 +898,16 @@
for (int i = 0; i < size; i++) {
final ApkLite apk = apks.get(splitNames[i]);
usesSplitNames[i] = apk.usesSplitName;
+ isFeatureSplits[i] = apk.isFeatureSplit;
+ configForSplits[i] = apk.configForSplit;
splitCodePaths[i] = apk.codePath;
splitRevisionCodes[i] = apk.revisionCode;
}
}
final String codePath = packageDir.getAbsolutePath();
- return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
- splitRevisionCodes);
+ return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames,
+ configForSplits, splitCodePaths, splitRevisionCodes);
}
/**
@@ -1069,42 +1085,6 @@
}
}
- private static SparseIntArray buildSplitDependencyTree(PackageLite pkg)
- throws PackageParserException {
- SparseIntArray splitDependencies = new SparseIntArray();
- for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
- final String splitDependency = pkg.usesSplitNames[splitIdx];
- if (splitDependency != null) {
- final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
- if (depIdx < 0) {
- throw new PackageParserException(
- PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
- + splitDependency + "', which is missing.");
- }
- splitDependencies.put(splitIdx, depIdx);
- }
- }
-
- // Verify that there are no cycles.
- final BitSet bitset = new BitSet();
- for (int i = 0; i < splitDependencies.size(); i++) {
- int splitIdx = splitDependencies.keyAt(i);
-
- bitset.clear();
- while (splitIdx != -1) {
- if (bitset.get(splitIdx)) {
- throw new PackageParserException(
- PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Cycle detected in split dependencies.");
- }
- bitset.set(splitIdx);
- splitIdx = splitDependencies.get(splitIdx, -1);
- }
- }
- return splitDependencies.size() != 0 ? splitDependencies : null;
- }
-
/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs sanity checking, such as requiring
@@ -1122,11 +1102,15 @@
}
// Build the split dependency tree.
- SparseIntArray splitDependencies = null;
+ SparseArray<int[]> splitDependencies = null;
final SplitAssetLoader assetLoader;
if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
- splitDependencies = buildSplitDependencyTree(lite);
- assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+ try {
+ splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
+ assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+ } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
+ }
} else {
assetLoader = new DefaultSplitAssetLoader(lite, flags);
}
@@ -1762,6 +1746,8 @@
boolean use32bitAbi = false;
boolean extractNativeLibs = true;
boolean isolatedSplits = false;
+ boolean isFeatureSplit = false;
+ String configForSplit = null;
String usesSplitName = null;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1777,6 +1763,10 @@
coreApp = attrs.getAttributeBooleanValue(i, false);
} else if (attr.equals("isolatedSplits")) {
isolatedSplits = attrs.getAttributeBooleanValue(i, false);
+ } else if (attr.equals("configForSplit")) {
+ configForSplit = attrs.getAttributeValue(i);
+ } else if (attr.equals("isFeatureSplit")) {
+ isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
}
}
@@ -1831,10 +1821,10 @@
}
}
- return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
- versionCode, revisionCode, installLocation, verifiers, signatures,
- certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
- isolatedSplits);
+ return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+ configForSplit, usesSplitName, versionCode, revisionCode, installLocation,
+ verifiers, signatures, certificates, coreApp, debuggable, multiArch, use32bitAbi,
+ extractNativeLibs, isolatedSplits);
}
/**
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 4df90eb..16023f0 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -18,10 +18,11 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+import android.annotation.NonNull;
import android.content.pm.PackageParser;
import android.content.res.AssetManager;
import android.os.Build;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import libcore.io.IoUtils;
@@ -34,49 +35,31 @@
* @hide
*/
public class SplitAssetDependencyLoader
- extends SplitDependencyLoaderHelper<PackageParser.PackageParserException>
+ extends SplitDependencyLoader<PackageParser.PackageParserException>
implements SplitAssetLoader {
- private static final int BASE_ASSET_PATH_IDX = -1;
- private final String mBasePath;
- private final String[] mSplitNames;
private final String[] mSplitPaths;
private final int mFlags;
- private String[] mCachedBasePaths;
- private AssetManager mCachedBaseAssetManager;
-
- private String[][] mCachedSplitPaths;
+ private String[][] mCachedPaths;
private AssetManager[] mCachedAssetManagers;
- public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
- int flags) {
+ public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+ SparseArray<int[]> dependencies, int flags) {
super(dependencies);
- mBasePath = pkg.baseCodePath;
- mSplitNames = pkg.splitNames;
- mSplitPaths = pkg.splitCodePaths;
+
+ // The base is inserted into index 0, so we need to shift all the splits by 1.
+ mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+ mSplitPaths[0] = pkg.baseCodePath;
+ System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
mFlags = flags;
- mCachedBasePaths = null;
- mCachedBaseAssetManager = null;
- mCachedSplitPaths = new String[mSplitNames.length][];
- mCachedAssetManagers = new AssetManager[mSplitNames.length];
+ mCachedPaths = new String[mSplitPaths.length][];
+ mCachedAssetManagers = new AssetManager[mSplitPaths.length];
}
@Override
protected boolean isSplitCached(int splitIdx) {
- if (splitIdx != -1) {
- return mCachedAssetManagers[splitIdx] != null;
- }
- return mCachedBaseAssetManager != null;
- }
-
- // Adds all non-code configuration splits for this split name. The split name is expected
- // to represent a feature split.
- private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
- for (int i = 0; i < mSplitNames.length; i++) {
- if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
- outAssetPaths.add(mSplitPaths[i]);
- }
- }
+ return mCachedAssetManagers[splitIdx] != null;
}
private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
@@ -107,45 +90,38 @@
}
@Override
- protected void constructSplit(int splitIdx, int parentSplitIdx) throws
- PackageParser.PackageParserException {
+ protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+ int parentSplitIdx) throws PackageParser.PackageParserException {
final ArrayList<String> assetPaths = new ArrayList<>();
- if (splitIdx == BASE_ASSET_PATH_IDX) {
- assetPaths.add(mBasePath);
- addAllConfigSplits(null, assetPaths);
- mCachedBasePaths = assetPaths.toArray(new String[assetPaths.size()]);
- mCachedBaseAssetManager = createAssetManagerWithPaths(mCachedBasePaths, mFlags);
- return;
- }
-
- if (parentSplitIdx == BASE_ASSET_PATH_IDX) {
- Collections.addAll(assetPaths, mCachedBasePaths);
- } else {
- Collections.addAll(assetPaths, mCachedSplitPaths[parentSplitIdx]);
+ if (parentSplitIdx >= 0) {
+ Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
}
assetPaths.add(mSplitPaths[splitIdx]);
- addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
- mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
- mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+ for (int configSplitIdx : configSplitIndices) {
+ assetPaths.add(mSplitPaths[configSplitIdx]);
+ }
+ mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+ mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
mFlags);
}
@Override
public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
- loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
- return mCachedBaseAssetManager;
+ loadDependenciesForSplit(0);
+ return mCachedAssetManagers[0];
}
@Override
public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
- loadDependenciesForSplit(idx);
- return mCachedAssetManagers[idx];
+ // Since we insert the base at position 0, and PackageParser keeps splits separate from
+ // the base, we need to adjust the index.
+ loadDependenciesForSplit(idx + 1);
+ return mCachedAssetManagers[idx + 1];
}
@Override
public void close() throws Exception {
- IoUtils.closeQuietly(mCachedBaseAssetManager);
for (AssetManager assets : mCachedAssetManagers) {
IoUtils.closeQuietly(assets);
}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java
new file mode 100644
index 0000000..3586546
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoader.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.split;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.content.pm.PackageParser;
+import android.util.IntArray;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ */
+public abstract class SplitDependencyLoader<E extends Exception> {
+ private final @NonNull SparseArray<int[]> mDependencies;
+
+ /**
+ * Construct a new SplitDependencyLoader. Meant to be called from the
+ * subclass constructor.
+ * @param dependencies The dependency tree of splits.
+ */
+ protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+ mDependencies = dependencies;
+ }
+
+ /**
+ * Traverses the dependency tree and constructs any splits that are not already
+ * cached. This routine short-circuits and skips the creation of splits closer to the
+ * root if they are cached, as reported by the subclass implementation of
+ * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+ * implementation of {@link #constructSplit(int, int[], int)}.
+ * @param splitIdx The index of the split to load. 0 represents the base Application.
+ */
+ protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+ // Quick check before any allocations are done.
+ if (isSplitCached(splitIdx)) {
+ return;
+ }
+
+ // Special case the base, since it has no dependencies.
+ if (splitIdx == 0) {
+ final int[] configSplitIndices = collectConfigSplitIndices(0);
+ constructSplit(0, configSplitIndices, -1);
+ return;
+ }
+
+ // Build up the dependency hierarchy.
+ final IntArray linearDependencies = new IntArray();
+ linearDependencies.add(splitIdx);
+
+ // Collect all the dependencies that need to be constructed.
+ // They will be listed from leaf to root.
+ while (true) {
+ // Only follow the first index into the array. The others are config splits and
+ // get loaded with the split.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps != null && deps.length > 0) {
+ splitIdx = deps[0];
+ } else {
+ splitIdx = -1;
+ }
+
+ if (splitIdx < 0 || isSplitCached(splitIdx)) {
+ break;
+ }
+
+ linearDependencies.add(splitIdx);
+ }
+
+ // Visit each index, from right to left (root to leaf).
+ int parentIdx = splitIdx;
+ for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+ final int idx = linearDependencies.get(i);
+ final int[] configSplitIndices = collectConfigSplitIndices(idx);
+ constructSplit(idx, configSplitIndices, parentIdx);
+ parentIdx = idx;
+ }
+ }
+
+ private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+ // The config splits appear after the first element.
+ final int[] deps = mDependencies.get(splitIdx);
+ if (deps == null || deps.length <= 1) {
+ return EmptyArray.INT;
+ }
+ return Arrays.copyOfRange(deps, 1, deps.length);
+ }
+
+ /**
+ * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+ * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+ * @param splitIdx The index of the split to check for in the cache.
+ * @return true if the split is cached and does not need to be constructed.
+ */
+ protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+ /**
+ * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+ * The result is expected to be cached by the subclass in its own structures.
+ * @param splitIdx The index of the split to construct. 0 represents the base Application.
+ * @param configSplitIndices The array of configuration splits to load along with this split.
+ * May be empty (length == 0) but never null.
+ * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+ * @throws E Subclass defined exception representing failure to construct a split.
+ */
+ protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+ @NonNull @IntRange(from = 1) int[] configSplitIndices,
+ @IntRange(from = -1) int parentSplitIdx) throws E;
+
+ public static class IllegalDependencyException extends Exception {
+ private IllegalDependencyException(String message) {
+ super(message);
+ }
+ }
+
+ private static int[] append(int[] src, int elem) {
+ if (src == null) {
+ return new int[] { elem };
+ }
+ int[] dst = Arrays.copyOf(src, src.length + 1);
+ dst[src.length] = elem;
+ return dst;
+ }
+
+ public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+ PackageParser.PackageLite pkg) throws IllegalDependencyException {
+ // The data structure that holds the dependencies. In PackageParser, splits are stored
+ // in their own array, separate from the base. We treat all paths as equals, so
+ // we need to insert the base as index 0, and shift all other splits.
+ final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+ // The base depends on nothing.
+ splitDependencies.put(0, new int[] {-1});
+
+ // First write out the <uses-split> dependencies. These must appear first in the
+ // array of ints, as is convention in this class.
+ for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+ if (!pkg.isFeatureSplits[splitIdx]) {
+ // Non-feature splits don't have dependencies.
+ continue;
+ }
+
+ // Implicit dependency on the base.
+ final int targetIdx;
+ final String splitDependency = pkg.usesSplitNames[splitIdx];
+ if (splitDependency != null) {
+ final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+ if (depIdx < 0) {
+ throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+ + "' requires split '" + splitDependency + "', which is missing.");
+ }
+ targetIdx = depIdx + 1;
+ } else {
+ // Implicitly depend on the base.
+ targetIdx = 0;
+ }
+ splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+ }
+
+ // Write out the configForSplit reverse-dependencies. These appear after the <uses-split>
+ // dependencies and are considered leaves.
+ //
+ // At this point, all splits in splitDependencies have the first element in their array set.
+ for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+ if (pkg.isFeatureSplits[splitIdx]) {
+ // Feature splits are not configForSplits.
+ continue;
+ }
+
+ // Implicit feature for the base.
+ final int targetSplitIdx;
+ final String configForSplit = pkg.configForSplit[splitIdx];
+ if (configForSplit != null) {
+ final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+ if (depIdx < 0) {
+ throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+ + "' targets split '" + configForSplit + "', which is missing.");
+ }
+
+ if (!pkg.isFeatureSplits[depIdx]) {
+ throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+ + "' declares itself as configuration split for a non-feature split '"
+ + pkg.splitNames[depIdx] + "'");
+ }
+ targetSplitIdx = depIdx + 1;
+ } else {
+ targetSplitIdx = 0;
+ }
+ splitDependencies.put(targetSplitIdx,
+ append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+ }
+
+ // Verify that there are no cycles.
+ final BitSet bitset = new BitSet();
+ for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+ int splitIdx = splitDependencies.keyAt(i);
+
+ bitset.clear();
+ while (splitIdx != -1) {
+ // Check if this split has been visited yet.
+ if (bitset.get(splitIdx)) {
+ throw new IllegalDependencyException("Cycle detected in split dependencies.");
+ }
+
+ // Mark the split so that if we visit it again, we no there is a cycle.
+ bitset.set(splitIdx);
+
+ // Follow the first dependency only, the others are leaves by definition.
+ final int[] deps = splitDependencies.get(splitIdx);
+ splitIdx = deps != null ? deps[0] : -1;
+ }
+ }
+ return splitDependencies;
+ }
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
deleted file mode 100644
index b493480..0000000
--- a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.pm.split;
-
-import android.annotation.Nullable;
-import android.util.IntArray;
-import android.util.SparseIntArray;
-
-/**
- * A helper class that implements the dependency tree traversal for splits. Callbacks
- * are implemented by subclasses to notify whether a split has already been constructed
- * and is cached, and to actually create the split requested.
- *
- * This helper is meant to be subclassed so as to reduce the number of allocations
- * needed to make use of it.
- *
- * All inputs and outputs are assumed to be indices into an array of splits.
- *
- * @hide
- */
-public abstract class SplitDependencyLoaderHelper<E extends Exception> {
- @Nullable private final SparseIntArray mDependencies;
-
- /**
- * Construct a new SplitDependencyLoaderHelper. Meant to be called from the
- * subclass constructor.
- * @param dependencies The dependency tree of splits. Can be null, which leads to
- * just the implicit dependency of all splits on the base.
- */
- protected SplitDependencyLoaderHelper(@Nullable SparseIntArray dependencies) {
- mDependencies = dependencies;
- }
-
- /**
- * Traverses the dependency tree and constructs any splits that are not already
- * cached. This routine short-circuits and skips the creation of splits closer to the
- * root if they are cached, as reported by the subclass implementation of
- * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
- * implementation of {@link #constructSplit(int, int)}.
- * @param splitIdx The index of the split to load. Can be -1, which represents the
- * base Application.
- */
- protected void loadDependenciesForSplit(int splitIdx) throws E {
- // Quick check before any allocations are done.
- if (isSplitCached(splitIdx)) {
- return;
- }
-
- final IntArray linearDependencies = new IntArray();
- linearDependencies.add(splitIdx);
-
- // Collect all the dependencies that need to be constructed.
- // They will be listed from leaf to root.
- while (splitIdx >= 0) {
- splitIdx = mDependencies != null ? mDependencies.get(splitIdx, -1) : -1;
- if (isSplitCached(splitIdx)) {
- break;
- }
- linearDependencies.add(splitIdx);
- }
-
- // Visit each index, from right to left (root to leaf).
- int parentIdx = splitIdx;
- for (int i = linearDependencies.size() - 1; i >= 0; i--) {
- final int idx = linearDependencies.get(i);
- constructSplit(idx, parentIdx);
- parentIdx = idx;
- }
- }
-
- /**
- * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
- * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
- * @param splitIdx The index of the split to check for in the cache.
- * @return true if the split is cached and does not need to be constructed.
- */
- protected abstract boolean isSplitCached(int splitIdx);
-
- /**
- * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
- * The result is expected to be cached by the subclass in its own structures.
- * @param splitIdx The index of the split to construct. Can be -1, which represents the
- * base Application.
- * @param parentSplitIdx The index of the parent split. Can be -1, which represents the
- * base Application.
- * @throws E
- */
- protected abstract void constructSplit(int splitIdx, int parentSplitIdx) throws E;
-
- /**
- * Returns true if `splitName` represents a Configuration split of `featureSplitName`.
- *
- * A Configuration split's name is prefixed with the associated Feature split's name
- * or the empty string if the split is for the base Application APK. It is then followed by the
- * dollar sign character "$" and some unique string that should represent the configurations
- * the split contains.
- *
- * Example:
- * <table>
- * <tr>
- * <th>Feature split name</th>
- * <th>Configuration split name: xhdpi</th>
- * <th>Configuration split name: fr-rFR</th>
- * </tr>
- * <tr>
- * <td>(base APK)</td>
- * <td><code>$xhdpi</code></td>
- * <td><code>$fr-rFR</code></td>
- * </tr>
- * <tr>
- * <td><code>Extras</code></td>
- * <td><code>Extras$xhdpi</code></td>
- * <td><code>Extras$fr-rFR</code></td>
- * </tr>
- * </table>
- *
- * @param splitName The name of the split to check.
- * @param featureSplitName The name of the Feature split. May be null or "" if checking
- * the base Application APK.
- * @return true if the splitName represents a Configuration split of featureSplitName.
- */
- protected static boolean isConfigurationSplitOf(String splitName, String featureSplitName) {
- if (featureSplitName == null || featureSplitName.length() == 0) {
- // We are looking for configuration splits of the base, which have some legacy support.
- if (splitName.startsWith("config_")) {
- return true;
- } else if (splitName.startsWith("$")) {
- return true;
- } else {
- return false;
- }
- } else {
- return splitName.startsWith(featureSplitName + "$");
- }
- }
-}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 67050f7..cf6bd9e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1014,6 +1014,15 @@
<p>The default value of this attribute is <code>false</code>. -->
<attr name="isolatedSplits" format="boolean" />
+ <!-- If set to <code>true</code>, indicates to the platform that this APK is
+ a 'feature' split and that it implicitly depends on the base APK. This distinguishes
+ this split APK from a 'configuration' split, which provides resource overrides
+ for a particular 'feature' split. Only useful when the base APK specifies
+ <code>android:isolatedSplits="true"</code>.
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="isFeatureSplit" format="boolean" />
+
<!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
{@code <application>} tag. If specified on the {@code <application>}
tag these will be considered defaults for all activities in the
@@ -1286,6 +1295,7 @@
<attr name="sharedUserLabel" />
<attr name="installLocation" />
<attr name="isolatedSplits" />
+ <attr name="isFeatureSplit" />
<attr name="targetSandboxVersion" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2897c62..f965c69 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2807,6 +2807,7 @@
<public name="importantForAutofill" />
<public name="recycleEnabled"/>
<public name="isStatic" />
+ <public name="isFeatureSplit" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index b8d95e4..359cfac 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4765,7 +4765,6 @@
&& (targetTypeLen = attrPrivate.size())
);
}
- break;
}
return 0;
}
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 8e9741e..94a2a14 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -44,8 +44,8 @@
density = 0x7f030002,
// From feature
- test3 = 0x7f080000,
- test4 = 0x7f080001,
+ test3 = 0x80020000,
+ test4 = 0x80020001,
};
};
@@ -57,7 +57,7 @@
ref2 = 0x7f040003,
// From feature
- number3 = 0x7f090000,
+ number3 = 0x80030000,
};
};
diff --git a/libs/androidfw/tests/data/feature/AndroidManifest.xml b/libs/androidfw/tests/data/feature/AndroidManifest.xml
index c972372..12ca5b6 100644
--- a/libs/androidfw/tests/data/feature/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/feature/AndroidManifest.xml
@@ -15,5 +15,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.basic">
+ package="com.android.basic"
+ featureName="feature">
</manifest>
diff --git a/libs/androidfw/tests/data/feature/build b/libs/androidfw/tests/data/feature/build
index 6ed3e41..aa2f716 100755
--- a/libs/androidfw/tests/data/feature/build
+++ b/libs/androidfw/tests/data/feature/build
@@ -19,4 +19,10 @@
PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
-aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --feature-of ../basic/basic.apk -F feature.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link -o feature.apk \
+ --manifest AndroidManifest.xml \
+ -I $PATH_TO_FRAMEWORK_RES \
+ -I ../basic/basic.apk \
+ --package-id 0x80 \
+ compiled.flata
diff --git a/libs/androidfw/tests/data/feature/feature.apk b/libs/androidfw/tests/data/feature/feature.apk
index 767fed6..a0dae38 100644
--- a/libs/androidfw/tests/data/feature/feature.apk
+++ b/libs/androidfw/tests/data/feature/feature.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/feature/res/values/values.xml b/libs/androidfw/tests/data/feature/res/values/values.xml
index 59f7d93..43e1517 100644
--- a/libs/androidfw/tests/data/feature/res/values/values.xml
+++ b/libs/androidfw/tests/data/feature/res/values/values.xml
@@ -15,13 +15,12 @@
-->
<resources>
- <!-- Features are offset, so 7f020000 will become 7f080000 at runtime. -->
- <public type="string" name="test3" id="0x7f020000" />
+ <public type="string" name="test3" id="0x80020000" />
<string name="test3">test3</string>
- <public type="string" name="test4" id="0x7f020001" />
+ <public type="string" name="test4" id="0x80020001" />
<string name="test4">test4</string>
- <public type="integer" name="number3" id="0x7f030000" />
+ <public type="integer" name="number3" id="0x80030000" />
<integer name="number3">200</integer>
</resources>
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0ec85aa..2e4a3a3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -915,7 +915,7 @@
// 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, null,
+ final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
splitPaths.toArray(new String[splitPaths.size()]), null);
final boolean isForwardLocked =
(params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 751d9af..25a596a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -171,7 +171,8 @@
if (file.isFile()) {
try {
ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+ null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
pkgLite, false, params.sessionParams.abiOverride));
} catch (PackageParserException | IOException e) {
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 1e488f7..9db21aa 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -23,30 +23,22 @@
namespace aapt {
-/**
- * Holds basic information about the app being built. Most of this information
- * will come from the app's AndroidManifest.
- */
+// Information relevant to building an app, parsed from the app's AndroidManifest.xml.
struct AppInfo {
- /**
- * App's package name.
- */
+ // The app's package name.
std::string package;
- /**
- * The App's minimum SDK version.
- */
+ // The app's minimum SDK version, if it is defined.
Maybe<std::string> min_sdk_version;
- /**
- * The Version code of the app.
- */
+ // The app's version code, if it is defined.
Maybe<uint32_t> version_code;
- /**
- * The revision code of the app.
- */
+ // The app's revision code, if it is defined.
Maybe<uint32_t> revision_code;
+
+ // The app's split name, if it is a split.
+ Maybe<std::string> split_name;
};
} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 1042111..1b4d5bb 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -751,70 +751,67 @@
return true;
}
- Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res,
- IDiagnostics* diag) {
+ Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
// Make sure the first element is <manifest> with package attribute.
- if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) {
- AppInfo app_info;
-
- if (!manifest_el->namespace_uri.empty() ||
- manifest_el->name != "manifest") {
- diag->Error(DiagMessage(xml_res->file.source)
- << "root tag must be <manifest>");
- return {};
- }
-
- xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
- if (!package_attr) {
- diag->Error(DiagMessage(xml_res->file.source)
- << "<manifest> must have a 'package' attribute");
- return {};
- }
-
- app_info.package = package_attr->value;
-
- if (xml::Attribute* version_code_attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
- Maybe<uint32_t> maybe_code =
- ResourceUtils::ParseInt(version_code_attr->value);
- if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(
- manifest_el->line_number))
- << "invalid android:versionCode '"
- << version_code_attr->value << "'");
- return {};
- }
- app_info.version_code = maybe_code.value();
- }
-
- if (xml::Attribute* revision_code_attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
- Maybe<uint32_t> maybe_code =
- ResourceUtils::ParseInt(revision_code_attr->value);
- if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(
- manifest_el->line_number))
- << "invalid android:revisionCode '"
- << revision_code_attr->value << "'");
- return {};
- }
- app_info.revision_code = maybe_code.value();
- }
-
- if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
- if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(
- xml::kSchemaAndroid, "minSdkVersion")) {
- app_info.min_sdk_version = min_sdk->value;
- }
- }
- return app_info;
+ xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get());
+ if (manifest_el == nullptr) {
+ return {};
}
- return {};
+
+ AppInfo app_info;
+
+ if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+ diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+ return {};
+ }
+
+ xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+ if (!package_attr) {
+ diag->Error(DiagMessage(xml_res->file.source)
+ << "<manifest> must have a 'package' attribute");
+ return {};
+ }
+ app_info.package = package_attr->value;
+
+ if (xml::Attribute* version_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
+ Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ << "invalid android:versionCode '" << version_code_attr->value << "'");
+ return {};
+ }
+ app_info.version_code = maybe_code.value();
+ }
+
+ if (xml::Attribute* revision_code_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
+ Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(revision_code_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ << "invalid android:revisionCode '" << revision_code_attr->value << "'");
+ return {};
+ }
+ app_info.revision_code = maybe_code.value();
+ }
+
+ if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+ if (!split_name_attr->value.empty()) {
+ app_info.split_name = split_name_attr->value;
+ }
+ }
+
+ if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (xml::Attribute* min_sdk =
+ uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+ app_info.min_sdk_version = min_sdk->value;
+ }
+ }
+ return app_info;
}
/**
- * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it
- * linked.
+ * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
* Postcondition: ResourceTable has only one package left. All others are
* stripped, or there is an error and false is returned.
*/
@@ -1367,45 +1364,44 @@
return true;
}
- std::unique_ptr<xml::XmlResource> GenerateSplitManifest(
- const AppInfo& app_info, const SplitConstraints& constraints) {
- std::unique_ptr<xml::XmlResource> doc =
- util::make_unique<xml::XmlResource>();
+ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
+ const SplitConstraints& constraints) {
+ std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
- std::unique_ptr<xml::Namespace> namespace_android =
- util::make_unique<xml::Namespace>();
+ std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>();
namespace_android->namespace_uri = xml::kSchemaAndroid;
namespace_android->namespace_prefix = "android";
- std::unique_ptr<xml::Element> manifest_el =
- util::make_unique<xml::Element>();
+ std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>();
manifest_el->name = "manifest";
- manifest_el->attributes.push_back(
- xml::Attribute{"", "package", app_info.package});
+ manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package});
if (app_info.version_code) {
- manifest_el->attributes.push_back(
- xml::Attribute{xml::kSchemaAndroid, "versionCode",
- std::to_string(app_info.version_code.value())});
+ manifest_el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, "versionCode", std::to_string(app_info.version_code.value())});
}
if (app_info.revision_code) {
- manifest_el->attributes.push_back(
- xml::Attribute{xml::kSchemaAndroid, "revisionCode",
- std::to_string(app_info.revision_code.value())});
+ manifest_el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, "revisionCode", std::to_string(app_info.revision_code.value())});
}
std::stringstream split_name;
+ if (app_info.split_name) {
+ split_name << app_info.split_name.value() << ".";
+ }
split_name << "config." << util::Joiner(constraints.configs, "_");
- manifest_el->attributes.push_back(
- xml::Attribute{"", "split", split_name.str()});
+ manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
- std::unique_ptr<xml::Element> application_el =
- util::make_unique<xml::Element>();
+ if (app_info.split_name) {
+ manifest_el->attributes.push_back(
+ xml::Attribute{"", "configForSplit", app_info.split_name.value()});
+ }
+
+ std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>();
application_el->name = "application";
- application_el->attributes.push_back(
- xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
+ application_el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
manifest_el->AppendChild(std::move(application_el));
namespace_android->AppendChild(std::move(manifest_el));
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 313fe45..0c19c7a 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -29,10 +29,7 @@
namespace aapt {
-/**
- * This is how PackageManager builds class names from AndroidManifest.xml
- * entries.
- */
+// This is how PackageManager builds class names from AndroidManifest.xml entries.
static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
SourcePathDiagnostics* diag) {
// We allow unqualified class names (ie: .HelloActivity)
@@ -90,6 +87,36 @@
};
}
+static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) {
+ constexpr const char* kFeatureSplit = "featureSplit";
+ constexpr const char* kIsFeatureSplit = "isFeatureSplit";
+
+ xml::Attribute* attr = el->FindAttribute({}, kFeatureSplit);
+ if (attr != nullptr) {
+ // Rewrite the featureSplit attribute to be "split". This is what the
+ // platform recognizes.
+ attr->name = "split";
+
+ // Now inject the android:isFeatureSplit="true" attribute.
+ xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kIsFeatureSplit);
+ if (attr != nullptr) {
+ if (!ResourceUtils::ParseBool(attr->value).value_or_default(false)) {
+ // The isFeatureSplit attribute is false, which conflicts with the use
+ // of "featureSplit".
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'featureSplit' used in <manifest> but 'android:isFeatureSplit' "
+ "is not 'true'");
+ return false;
+ }
+
+ // The attribute is already there and set to true, nothing to do.
+ } else {
+ el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, kIsFeatureSplit, "true"});
+ }
+ }
+ return true;
+}
+
static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute({}, "package");
if (!attr) {
@@ -97,31 +124,34 @@
<< "<manifest> tag is missing 'package' attribute");
return false;
} else if (ResourceUtils::IsReference(attr->value)) {
- diag->Error(
- DiagMessage(el->line_number)
- << "attribute 'package' in <manifest> tag must not be a reference");
+ diag->Error(DiagMessage(el->line_number)
+ << "attribute 'package' in <manifest> tag must not be a reference");
return false;
} else if (!util::IsJavaPackageName(attr->value)) {
diag->Error(DiagMessage(el->line_number)
- << "attribute 'package' in <manifest> tag is not a valid Java "
- "package name: '"
+ << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
<< attr->value << "'");
return false;
}
+
+ attr = el->FindAttribute({}, "split");
+ if (attr) {
+ if (!util::IsJavaPackageName(attr->value)) {
+ diag->Error(DiagMessage(el->line_number) << "attribute 'split' in <manifest> tag is not a "
+ "valid split name");
+ return false;
+ }
+ }
return true;
}
-/**
- * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
- * checking on it is manual.
- */
+// The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
+// checking on it is manual.
static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) {
- std::unique_ptr<BinaryPrimitive> result =
- ResourceUtils::TryParseBool(attr->value);
+ std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseBool(attr->value);
if (!result) {
- diag->Error(DiagMessage(el->line_number)
- << "attribute coreApp must be a boolean");
+ diag->Error(DiagMessage(el->line_number) << "attribute coreApp must be a boolean");
return false;
}
attr->compiled_value = std::move(result);
@@ -172,8 +202,7 @@
}
if (options_.rename_instrumentation_target_package) {
- if (!util::IsJavaPackageName(
- options_.rename_instrumentation_target_package.value())) {
+ if (!util::IsJavaPackageName(options_.rename_instrumentation_target_package.value())) {
diag->Error(DiagMessage()
<< "invalid instrumentation target package override '"
<< options_.rename_instrumentation_target_package.value()
@@ -203,6 +232,7 @@
// Manifest actions.
xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
+ manifest_action.Action(AutoGenerateIsFeatureSplit);
manifest_action.Action(VerifyManifest);
manifest_action.Action(FixCoreAppAttribute);
manifest_action.Action([&](xml::Element* el) -> bool {
@@ -276,6 +306,7 @@
manifest_action["compatible-screens"]["screen"];
manifest_action["supports-gl-texture"];
manifest_action["meta-data"] = meta_data_action;
+ manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
// Application actions.
xml::XmlNodeAction& application_action = manifest_action["application"];
@@ -311,15 +342,13 @@
public:
using xml::Visitor::Visit;
- explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
- : package_(package) {}
+ explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : package_(package) {}
void Visit(xml::Element* el) override {
for (xml::Attribute& attr : el->attributes) {
if (attr.namespace_uri == xml::kSchemaAndroid &&
class_attributes_.find(attr.name) != class_attributes_.end()) {
- if (Maybe<std::string> new_value =
- util::GetFullyQualifiedClassName(package_, attr.value)) {
+ if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package_, attr.value)) {
attr.value = std::move(new_value.value());
}
}
@@ -334,8 +363,7 @@
std::unordered_set<StringPiece> class_attributes_ = {"name"};
};
-static bool RenameManifestPackage(const StringPiece& package_override,
- xml::Element* manifest_el) {
+static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
// We've already verified that the manifest element is present, with a package
@@ -358,8 +386,7 @@
return false;
}
- if ((options_.min_sdk_version_default ||
- options_.target_sdk_version_default) &&
+ if ((options_.min_sdk_version_default || options_.target_sdk_version_default) &&
root->FindChild({}, "uses-sdk") == nullptr) {
// Auto insert a <uses-sdk> element. This must be inserted before the
// <application> tag. The device runtime PackageParser will make SDK version
@@ -374,8 +401,7 @@
return false;
}
- if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist,
- context->GetDiagnostics(), doc)) {
+ if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
return false;
}
@@ -383,8 +409,7 @@
// Rename manifest package outside of the XmlActionExecutor.
// We need to extract the old package name and FullyQualify all class
// names.
- if (!RenameManifestPackage(options_.rename_manifest_package.value(),
- root)) {
+ if (!RenameManifestPackage(options_.rename_manifest_package.value(), root)) {
return false;
}
}