Merge "Allow framework-res APK splits from Apexes"

This commit is contained in:
Pedro Loureiro
2022-01-25 15:28:01 +00:00
committed by Android (Google) Code Review
9 changed files with 155 additions and 44 deletions

View File

@@ -218,7 +218,7 @@ public class PackageParsingPerfTest {
})
override fun parseImpl(file: File) =
parser.parsePackage(input.get()!!.reset(), file, 0).result
parser.parsePackage(input.get()!!.reset(), file, 0, null).result
as ParsingPackageImpl
}

View File

@@ -78,6 +78,7 @@ public class ApkLiteParseUtils {
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
private static final String TAG_APPLICATION = "application";
private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
private static final String TAG_PROFILEABLE = "profileable";
@@ -101,7 +102,7 @@ public class ApkLiteParseUtils {
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
File packageFile, int flags) {
if (packageFile.isDirectory()) {
return parseClusterPackageLite(input, packageFile, flags);
return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
} else {
return parseMonolithicPackageLite(input, packageFile, flags);
}
@@ -134,21 +135,44 @@ public class ApkLiteParseUtils {
/**
* Parse lightweight details about a directory of APKs.
*
* @param packageDirOrApk is the folder that contains split apks for a regular app or the
* framework-res.apk for framwork-res splits (in which case the
* splits come in the <code>frameworkSplits</code> parameter)
*/
public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
File packageDir, int flags) {
final File[] files = packageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
File packageDirOrApk, List<File> frameworkSplits, int flags) {
final File[] files;
final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
if (parsingFrameworkSplits) {
if (ArrayUtils.isEmpty(frameworkSplits)) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
}
files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
// we also want to process the base apk so add it to the array
files[files.length - 1] = packageDirOrApk;
} else {
files = packageDirOrApk.listFiles();
if (ArrayUtils.isEmpty(files)) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
}
// Apk directory is directly nested under the current directory
if (files.length == 1 && files[0].isDirectory()) {
return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
}
}
// Apk directory is directly nested under the current directory
if (files.length == 1 && files[0].isDirectory()) {
return parseClusterPackageLite(input, files[0], flags);
if (parsingFrameworkSplits) {
// disable the flag for checking the certificates of the splits. We know they
// won't match, but we rely on the mainline apex to be safe if it was installed
flags = flags & ~PARSE_COLLECT_CERTIFICATES;
}
String packageName = null;
int versionCode = 0;
ApkLite baseApk = null;
final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
@@ -161,6 +185,10 @@ public class ApkLiteParseUtils {
}
final ApkLite lite = result.getResult();
if (parsingFrameworkSplits && file == files[files.length - 1]) {
baseApk = lite;
break;
}
// Assert that all package names and version codes are
// consistent with the first one we encounter.
if (packageName == null) {
@@ -172,7 +200,8 @@ public class ApkLiteParseUtils {
"Inconsistent package " + lite.getPackageName() + " in " + file
+ "; expected " + packageName);
}
if (versionCode != lite.getVersionCode()) {
// we allow version codes that do not match for framework splits
if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Inconsistent version " + lite.getVersionCode() + " in " + file
+ "; expected " + versionCode);
@@ -187,12 +216,15 @@ public class ApkLiteParseUtils {
}
}
}
// baseApk is set in the last iteration of the for each loop when we are parsing
// frameworkRes splits or needs to be done now otherwise
if (!parsingFrameworkSplits) {
baseApk = apks.remove(null);
}
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
final ApkLite baseApk = apks.remove(null);
return composePackageLiteFromApks(input, packageDir, baseApk, apks);
return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
}
/**

View File

@@ -30,8 +30,10 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
import android.annotation.Nullable;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
@@ -91,6 +93,29 @@ final class InitAndSystemPackageHelper {
mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
}
private List<File> getFrameworkResApkSplitFiles() {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
try {
final List<File> splits = new ArrayList<>();
final List<ApexManager.ActiveApexInfo> activeApexInfos =
mPm.mApexManager.getActiveApexInfos();
for (int i = 0; i < activeApexInfos.size(); i++) {
ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
if (splitsFolder.isDirectory()) {
for (File file : splitsFolder.listFiles()) {
if (ApkLiteParseUtils.isApkFile(file)) {
splits.add(file);
}
}
}
}
return splits;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private List<ScanPartition> getSystemScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
@@ -184,7 +209,8 @@ final class InitAndSystemPackageHelper {
if (!mPm.isOnlyCoreApps()) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
mScanFlags | SCAN_REQUIRE_KNOWN, 0,
packageParser, executorService);
}
@@ -247,12 +273,14 @@ final class InitAndSystemPackageHelper {
if (partition.getOverlayFolder() == null) {
continue;
}
scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
mSystemScanFlags | partition.scanFlag, 0,
scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
packageParser, executorService);
}
scanDirTracedLI(frameworkDir, mSystemParseFlags,
List<File> frameworkSplits = getFrameworkResApkSplitFiles();
scanDirTracedLI(frameworkDir, frameworkSplits,
mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
@@ -263,12 +291,13 @@ final class InitAndSystemPackageHelper {
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
mSystemParseFlags,
mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
packageParser, executorService);
}
scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
mSystemScanFlags | partition.scanFlag, 0,
scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
packageParser, executorService);
}
}
@@ -285,12 +314,13 @@ final class InitAndSystemPackageHelper {
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
final int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
currentTime, packageParser, executorService);
mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
scanFlags, currentTime, packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

View File

@@ -3406,8 +3406,9 @@ final class InstallPackageHelper {
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
int scanFlags, long currentTime, PackageParser2 packageParser,
ExecutorService executorService) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
@@ -3419,7 +3420,7 @@ final class InstallPackageHelper {
+ " flags=0x" + Integer.toHexString(parseFlags));
}
ParallelPackageParser parallelPackageParser =
new ParallelPackageParser(packageParser, executorService);
new ParallelPackageParser(packageParser, executorService, frameworkSplits);
// Submit files for parsing in parallel
int fileCount = 0;

View File

@@ -27,6 +27,7 @@ import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import java.io.File;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -54,9 +55,17 @@ class ParallelPackageParser {
private final ExecutorService mExecutorService;
private final List<File> mFrameworkSplits;
ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
this(packageParser, executorService, /* frameworkSplits= */ null);
}
ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
List<File> frameworkSplits) {
mPackageParser = packageParser;
mExecutorService = executorService;
mFrameworkSplits = frameworkSplits;
}
static class ParseResult {
@@ -125,6 +134,6 @@ class ParallelPackageParser {
@VisibleForTesting
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
return mPackageParser.parsePackage(scanFile, parseFlags, true);
return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
}
}

View File

@@ -22,9 +22,6 @@ import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.pkg.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
@@ -41,6 +38,9 @@ import com.android.server.pm.PackageManagerException;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.pkg.parsing.ParsingUtils;
import java.io.File;
import java.util.List;
@@ -147,6 +147,15 @@ public class PackageParser2 implements AutoCloseable {
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageManagerException {
return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
}
/**
* TODO(b/135203078): Document new package parsing
*/
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
List<File> frameworkSplits) throws PackageManagerException {
if (useCaches && mCacher != null) {
ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
if (parsed != null) {
@@ -156,7 +165,8 @@ public class PackageParser2 implements AutoCloseable {
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
frameworkSplits);
if (result.isError()) {
throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
result.getException());

View File

@@ -65,6 +65,7 @@ import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -231,6 +232,8 @@ public class ParsingPackageUtils {
* of required system property within the overlay tag.
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
public static final int PARSE_CHATTY = 1 << 31;
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -242,6 +245,7 @@ public class ParsingPackageUtils {
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
PARSE_FRAMEWORK_RES_SPLITS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ParseFlags {}
@@ -290,7 +294,7 @@ public class ParsingPackageUtils {
return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
}
});
result = parser.parsePackage(input, file, parseFlags);
result = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
if (result.isError()) {
return input.error(result);
}
@@ -344,26 +348,44 @@ public class ParsingPackageUtils {
* not check whether {@code packageFile} has changed since the last parse, it's up to callers to
* do so.
*/
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
if (packageFile.isDirectory()) {
return parseClusterPackage(input, packageFile, flags);
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
List<File> frameworkSplits) {
if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
&& frameworkSplits.size() > 0
&& packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
return parseClusterPackage(input, packageFile, frameworkSplits, flags);
} else if (packageFile.isDirectory()) {
return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
} else {
return parseMonolithicPackage(input, packageFile, flags);
}
}
/**
* Parse all APKs contained in the given directory, treating them as a single package. This also
* performs validity checking, such as requiring identical package name and version codes, a
* single base APK, and unique split names.
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs validity checking, such as requiring
* identical package name and version codes, a single base APK, and unique
* split names.
* <p>
* Can also be passed the framework-res.apk file and a list of split apks coming from apexes
* (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
* even if they are in different folders. Note that this code path may have other behaviour
* differences.
* <p>
* Note that this <em>does not</em> perform signature verification; that must be done separately
* in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}.
*/
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
int flags) {
List<File> frameworkSplits, int flags) {
// parseClusterPackageLite should receive no flags (0) for regular splits but we want to
// pass the flags for framework splits
int liteParseFlags = 0;
if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
liteParseFlags = flags;
}
final ParseResult<PackageLite> liteResult =
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
@@ -2990,9 +3012,12 @@ public class ParsingPackageUtils {
}
signingDetails = result.getResult();
final File frameworkRes = new File(Environment.getRootDirectory(),
"framework/framework-res.apk");
boolean isFrameworkResSplit = frameworkRes.getAbsolutePath()
.equals(pkg.getBaseApkPath());
String[] splitCodePaths = pkg.getSplitCodePaths();
if (!ArrayUtils.isEmpty(splitCodePaths)) {
if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) {
for (int i = 0; i < splitCodePaths.length; i++) {
result = getSigningDetails(
input,

View File

@@ -521,6 +521,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val parsedPackage = parseResult.hideAsParsed() as ParsedPackage
whenever(mocks.packageParser.parsePackage(
or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
whenever(mocks.packageParser.parsePackage(
or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
return parsedPackage
}

View File

@@ -33,6 +33,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
@@ -121,7 +122,8 @@ class PackageManagerServiceBootTest {
whenever(rule.mocks().packageParser.parsePackage(
argThat { path: File -> path.path.contains("a.data.package") },
anyInt(),
anyBoolean()))
anyBoolean(),
any()))
.thenThrow(PackageManagerException(
PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
val pm = createPackageManagerService()