Caches parsed package info for APEX

Leverage PackageParser2 and ParallelPackageParser in
package manager service to parse APEX files when device
boots up. It caches parsed results and enables parallel
parsering to improve APEX parsing performance.

Bug: 151296698
Test: atest ApexManagerTest
Test: atest RollbackManagerHostTest
Change-Id: Ie5f49b5b3747b4785bafc14b48a77232373488f0
This commit is contained in:
Rhed Jao
2020-03-17 22:35:33 +08:00
committed by Philip P. Moltmann
parent 93032f6f95
commit 9541b09160
3 changed files with 144 additions and 110 deletions

View File

@@ -16,22 +16,22 @@
package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.parsing.PackageInfoWithoutStateUtils;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -44,8 +44,9 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -62,6 +63,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
/**
@@ -139,7 +141,14 @@ public abstract class ApexManager {
*/
public abstract List<ActiveApexInfo> getActiveApexInfos();
abstract void systemReady(Context context);
/**
* Called by package manager service to scan apex package files when device boots up.
*
* @param packageParser The package parser which supports caches.
* @param executorService An executor to support parallel package parsing.
*/
abstract void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService);
/**
* Retrieves information about an APEX package.
@@ -411,104 +420,92 @@ public abstract class ApexManager {
}
@Override
void systemReady(Context context) {
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Post populateAllPackagesCacheIfNeeded to a background thread, since it's
// expensive to run it in broadcast handler thread.
BackgroundThread.getHandler().post(() -> populateAllPackagesCacheIfNeeded());
context.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
private void populatePackageNameToApexModuleNameIfNeeded() {
synchronized (mLock) {
if (mPackageNameToApexModuleName != null) {
return;
}
try {
mPackageNameToApexModuleName = new ArrayMap<>();
final ApexInfo[] allPkgs = mApexService.getAllPackages();
for (int i = 0; i < allPkgs.length; i++) {
ApexInfo ai = allPkgs[i];
PackageParser.PackageLite pkgLite;
try {
File apexFile = new File(ai.modulePath);
pkgLite = PackageParser.parsePackageLite(apexFile, 0);
} catch (PackageParser.PackageParserException pe) {
throw new IllegalStateException("Unable to parse: "
+ ai.modulePath, pe);
}
mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
throw new RuntimeException(re);
void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackagesTraced");
try {
synchronized (mLock) {
scanApexPackagesInternalLocked(packageParser, executorService);
}
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
return;
}
try {
mAllPackagesCache = new ArrayList<>();
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
final ApexInfo[] allPkgs = mApexService.getAllPackages();
for (ApexInfo ai : allPkgs) {
// If the device is using flattened APEX, don't report any APEX
// packages since they won't be managed or updated by PackageManager.
if ((new File(ai.modulePath)).isDirectory()) {
break;
}
int flags = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.GET_SIGNATURES;
PackageParser.Package pkg;
try {
File apexFile = new File(ai.modulePath);
PackageParser pp = new PackageParser();
pkg = pp.parsePackage(apexFile, flags, false);
PackageParser.collectCertificates(pkg, false);
} catch (PackageParser.PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
@GuardedBy("mLock")
private void scanApexPackagesInternalLocked(PackageParser2 packageParser,
ExecutorService executorService) {
final ApexInfo[] allPkgs;
try {
mAllPackagesCache = new ArrayList<>();
mPackageNameToApexModuleName = new ArrayMap<>();
allPkgs = mApexService.getAllPackages();
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
}
if (allPkgs.length == 0) {
return;
}
int flags = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.GET_SIGNATURES;
ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>();
ParallelPackageParser parallelPackageParser =
new ParallelPackageParser(packageParser, executorService);
final PackageInfo packageInfo =
PackageParser.generatePackageInfo(pkg, ai, flags);
mAllPackagesCache.add(packageInfo);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two active packages have the same name: "
+ packageInfo.packageName);
}
activePackagesSet.add(packageInfo.packageName);
}
if (ai.isFactory) {
if (factoryPackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two factory packages have the same name: "
+ packageInfo.packageName);
}
factoryPackagesSet.add(packageInfo.packageName);
}
for (ApexInfo ai : allPkgs) {
File apexFile = new File(ai.modulePath);
parallelPackageParser.submit(apexFile, flags);
parsingApexInfo.put(apexFile, ai);
}
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
// Process results one by one
for (int i = 0; i < parsingApexInfo.size(); i++) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
if (throwable == null) {
final PackageInfo packageInfo = PackageInfoWithoutStateUtils.generate(
parseResult.parsedPackage, ai, flags);
if (packageInfo == null) {
throw new IllegalStateException("Unable to generate package info: "
+ ai.modulePath);
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
mAllPackagesCache.add(packageInfo);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two active packages have the same name: "
+ packageInfo.packageName);
}
activePackagesSet.add(packageInfo.packageName);
}
if (ai.isFactory) {
if (factoryPackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two factory packages have the same name: "
+ packageInfo.packageName);
}
factoryPackagesSet.add(packageInfo.packageName);
}
} else if (throwable instanceof PackageParser.PackageParserException) {
throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ ai.modulePath, throwable);
}
}
}
@Override
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
populateAllPackagesCacheIfNeeded();
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (PackageInfo packageInfo: mAllPackagesCache) {
@@ -525,7 +522,8 @@ public abstract class ApexManager {
@Override
List<PackageInfo> getActivePackages() {
populateAllPackagesCacheIfNeeded();
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
return mAllPackagesCache
.stream()
.filter(item -> isActive(item))
@@ -534,7 +532,8 @@ public abstract class ApexManager {
@Override
List<PackageInfo> getFactoryPackages() {
populateAllPackagesCacheIfNeeded();
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
return mAllPackagesCache
.stream()
.filter(item -> isFactory(item))
@@ -543,7 +542,8 @@ public abstract class ApexManager {
@Override
List<PackageInfo> getInactivePackages() {
populateAllPackagesCacheIfNeeded();
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
return mAllPackagesCache
.stream()
.filter(item -> !isActive(item))
@@ -553,7 +553,8 @@ public abstract class ApexManager {
@Override
boolean isApexPackage(String packageName) {
if (!isApexSupported()) return false;
populateAllPackagesCacheIfNeeded();
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
for (PackageInfo packageInfo : mAllPackagesCache) {
if (packageInfo.packageName.equals(packageName)) {
return true;
@@ -684,8 +685,9 @@ public abstract class ApexManager {
@Override
List<String> getApksInApex(String apexPackageName) {
populatePackageNameToApexModuleNameIfNeeded();
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
if (moduleName == null) {
return Collections.emptyList();
@@ -697,17 +699,19 @@ public abstract class ApexManager {
@Override
@Nullable
public String getApexModuleNameForPackageName(String apexPackageName) {
populatePackageNameToApexModuleNameIfNeeded();
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
return mPackageNameToApexModuleName.get(apexPackageName);
}
}
@Override
public long snapshotCeData(int userId, int rollbackId, String apexPackageName) {
populatePackageNameToApexModuleNameIfNeeded();
String apexModuleName;
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
}
if (apexModuleName == null) {
@@ -724,9 +728,10 @@ public abstract class ApexManager {
@Override
public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
populatePackageNameToApexModuleNameIfNeeded();
String apexModuleName;
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
}
if (apexModuleName == null) {
@@ -797,15 +802,7 @@ public abstract class ApexManager {
void dump(PrintWriter pw, @Nullable String packageName) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
try {
populateAllPackagesCacheIfNeeded();
ipw.println();
ipw.println("Active APEX packages:");
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
ipw.println("Inactive APEX packages:");
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
ipw.println("Factory APEX packages:");
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
ipw.increaseIndent();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = mApexService.getSessions();
@@ -834,6 +831,17 @@ public abstract class ApexManager {
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
if (mAllPackagesCache == null) {
ipw.println("APEX packages have not been scanned");
return;
}
ipw.println("Active APEX packages:");
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
ipw.println("Inactive APEX packages:");
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
ipw.println("Factory APEX packages:");
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
}
@@ -879,7 +887,8 @@ public abstract class ApexManager {
}
@Override
void systemReady(Context context) {
void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService) {
// No-op
}

View File

@@ -2900,6 +2900,9 @@ public class PackageManagerService extends IPackageManager.Stub
mMetrics, mCacheDir, mPackageParserCallback);
ExecutorService executorService = ParallelPackageParser.makeExecutorService();
// Prepare apex package info before scanning APKs, these information are needed when
// scanning apk in apex.
mApexManager.scanApexPackagesTraced(packageParser, executorService);
// Collect vendor/product/system_ext overlay packages. (Do this before scanning
// any apps.)
// For security and version matching reason, only consider overlay packages if they
@@ -20685,7 +20688,6 @@ public class PackageManagerService extends IPackageManager.Stub
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
mApexManager.systemReady(mContext);
mPackageDexOptimizer.systemReady();
mInjector.getStorageManagerInternal().addExternalStoragePolicy(

View File

@@ -43,6 +43,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
import com.android.server.pm.parsing.PackageParser2;
import org.junit.Before;
import org.junit.Test;
@@ -63,6 +64,7 @@ public class ApexManagerTest {
private static final int[] TEST_CHILD_SESSION_ID = {8888, 7777};
private ApexManager mApexManager;
private Context mContext;
private PackageParser2 mPackageParser2;
private IApexService mApexService = mock(IApexService.class);
@@ -70,11 +72,14 @@ public class ApexManagerTest {
public void setUp() throws RemoteException {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mApexManager = new ApexManager.ApexManagerImpl(mApexService);
mPackageParser2 = new PackageParser2(null, false, null, null, null);
}
@Test
public void testGetPackageInfo_setFlagsMatchActivePackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
final PackageInfo activePkgPi = mApexManager.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_ACTIVE_PACKAGE);
@@ -90,6 +95,8 @@ public class ApexManagerTest {
@Test
public void testGetPackageInfo_setFlagsMatchFactoryPackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
PackageInfo factoryPkgPi = mApexManager.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_FACTORY_PACKAGE);
@@ -105,6 +112,8 @@ public class ApexManagerTest {
@Test
public void testGetPackageInfo_setFlagsNone() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getPackageInfo(TEST_APEX_PKG, 0)).isNull();
}
@@ -112,6 +121,8 @@ public class ApexManagerTest {
@Test
public void testGetActivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getActivePackages()).isNotEmpty();
}
@@ -119,6 +130,8 @@ public class ApexManagerTest {
@Test
public void testGetActivePackages_noneActivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getActivePackages()).isEmpty();
}
@@ -126,6 +139,8 @@ public class ApexManagerTest {
@Test
public void testGetFactoryPackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getFactoryPackages()).isNotEmpty();
}
@@ -133,6 +148,8 @@ public class ApexManagerTest {
@Test
public void testGetFactoryPackages_noneFactoryPackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getFactoryPackages()).isEmpty();
}
@@ -140,6 +157,8 @@ public class ApexManagerTest {
@Test
public void testGetInactivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getInactivePackages()).isNotEmpty();
}
@@ -147,6 +166,8 @@ public class ApexManagerTest {
@Test
public void testGetInactivePackages_noneInactivePackages() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(true, false));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.getInactivePackages()).isEmpty();
}
@@ -154,6 +175,8 @@ public class ApexManagerTest {
@Test
public void testIsApexPackage() throws RemoteException {
when(mApexService.getAllPackages()).thenReturn(createApexInfo(false, true));
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
assertThat(mApexManager.isApexPackage(TEST_APEX_PKG)).isTrue();
}