Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e3976630 authored by JW Wang's avatar JW Wang Committed by Chun-Wei Wang
Browse files

Scan APEX packages

* Add a flag to toggle the feature. This makes it easy to compare the
  performance impact with and without the feature. The feature can
  be disabled if any regression is found.
* Add InstallPackageHelper#scanApexPackages to scan APEX packages
  during boot. APEX now goes through the same scan flow as APK.
  Note APEX is not registered with the system and its PackageInfo is
  still cached in ApexPackageInfo.
* For rebootless APEX, #scanSystemPackageLI is applied to the newly
  installed APEX. This is consistent with how we scan APEX during
  boot.

Duration of scanApexPackagesTraced(), measured on Pixel4a:
feature enabled: 110ms
feature disabled: 50ms

This is expected because we now have more packages to scan. We used to
scan APK only. Now both APK and APEX are scanned during boot.

Bug: 225756739
Test: atest com.android.server.pm.ApexManagerTest \
            GtsStagedInstallHostTestCases \
	    CtsStagedInstallHostTestCases \
	    StagedInstallInternalTest
Change-Id: If762100a08c9c59ae55a058f2ca42a06fcc6b80a
parent 9c443b1d
Loading
Loading
Loading
Loading
+10 −15
Original line number Diff line number Diff line
@@ -104,11 +104,11 @@ public abstract class ApexManager {

    static class ScanResult {
        public final ApexInfo apexInfo;
        public final ParsedPackage parsedPackage;
        public final AndroidPackage pkg;
        public final String packageName;
        ScanResult(ApexInfo apexInfo, ParsedPackage parsedPackage, String packageName) {
        ScanResult(ApexInfo apexInfo, AndroidPackage pkg, String packageName) {
            this.apexInfo = apexInfo;
            this.parsedPackage = parsedPackage;
            this.pkg = pkg;
            this.packageName = packageName;
        }
    }
@@ -357,8 +357,10 @@ public abstract class ApexManager {

    /**
     * Performs a non-staged install of the given {@code apexFile}.
     *
     * @return {@code ApeInfo} about the newly installed APEX package.
     */
    abstract void installPackage(File apexFile, PackageParser2 packageParser,
    abstract ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
            ApexPackageInfo apexPackageInfo) throws PackageManagerException;

    /**
@@ -470,7 +472,7 @@ public abstract class ApexManager {
                ApexInfo ai = scanResult.apexInfo;
                String packageName = scanResult.packageName;
                for (ParsedApexSystemService service :
                        scanResult.parsedPackage.getApexSystemServices()) {
                        scanResult.pkg.getApexSystemServices()) {
                    String minSdkVersion = service.getMinSdkVersion();
                    if (minSdkVersion != null && !UnboundedSdkLevel.isAtLeast(minSdkVersion)) {
                        Slog.d(TAG, String.format(
@@ -896,7 +898,7 @@ public abstract class ApexManager {
        }

        @Override
        void installPackage(File apexFile, PackageParser2 packageParser,
        ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
                ApexPackageInfo apexPackageInfo)
                throws PackageManagerException {
            try {
@@ -919,14 +921,7 @@ public abstract class ApexManager {
                            "It is forbidden to install new APEX packages");
                }
                checkApexSignature(existingApexPkg, newApexPkg);
                ApexInfo apexInfo = waitForApexService().installAndActivatePackage(
                        apexFile.getAbsolutePath());
                final ParsedPackage parsedPackage2 = packageParser.parsePackage(
                        new File(apexInfo.modulePath), flags, /* useCaches= */ false);
                final PackageInfo finalApexPkg = PackageInfoWithoutStateUtils.generate(
                        parsedPackage2, apexInfo, flags);
                // Installation was successful, time to update cached PackageInfo
                apexPackageInfo.notifyPackageInstalled(existingApexPkg, finalApexPkg);
                return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath());
            } catch (RemoteException e) {
                throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                        "apexservice not available");
@@ -1166,7 +1161,7 @@ public abstract class ApexManager {
        }

        @Override
        void installPackage(File apexFile, PackageParser2 packageParser,
        ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
                ApexPackageInfo apexPackageInfo) {
            throw new UnsupportedOperationException("APEX updates are not supported");
        }
+31 −4
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import com.android.internal.annotations.GuardedBy;
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.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;

@@ -50,6 +52,8 @@ import java.util.concurrent.ExecutorService;
 * including both APK and APEX. This class will no longer be needed when the migration is done.
 */
class ApexPackageInfo {
    public static final boolean ENABLE_FEATURE_SCAN_APEX = true;

    private static final String TAG = "ApexManager";
    private static final String VNDK_APEX_MODULE_NAME_PREFIX = "com.android.vndk.";

@@ -83,6 +87,12 @@ class ApexPackageInfo {
        }
    }

    void notifyScanResult(List<ApexManager.ScanResult> scanResults) {
        synchronized (mLock) {
            notifyScanResultLocked(scanResults);
        }
    }

    /**
     * Retrieves information about an APEX package.
     *
@@ -200,12 +210,29 @@ class ApexPackageInfo {
    }

    /**
     * Called by ApexManager to update cached PackageInfo when installing rebootless APEX.
     * Called to update cached PackageInfo when installing rebootless APEX.
     */
    void notifyPackageInstalled(PackageInfo oldApexPkg, PackageInfo newApexPkg) {
    void notifyPackageInstalled(ApexInfo apexInfo, PackageParser2 packageParser)
            throws PackageManagerException {
        final int flags = PackageManager.GET_META_DATA
                | PackageManager.GET_SIGNING_CERTIFICATES
                | PackageManager.GET_SIGNATURES;
        final ParsedPackage parsedPackage = packageParser.parsePackage(
                new File(apexInfo.modulePath), flags, /* useCaches= */ false);
        notifyPackageInstalled(apexInfo, parsedPackage.hideAsFinal());
    }

    void notifyPackageInstalled(ApexInfo apexInfo, AndroidPackage pkg) {
        final int flags = PackageManager.GET_META_DATA
                | PackageManager.GET_SIGNING_CERTIFICATES
                | PackageManager.GET_SIGNATURES;
        final PackageInfo newApexPkg = PackageInfoWithoutStateUtils.generate(
                pkg, apexInfo, flags);
        final String packageName = newApexPkg.packageName;
        synchronized (mLock) {
            for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
                if (mAllPackagesCache.get(i).equals(oldApexPkg)) {
                PackageInfo oldApexPkg = mAllPackagesCache.get(i);
                if (oldApexPkg.isActiveApex && oldApexPkg.packageName.equals(packageName)) {
                    if (isFactory(oldApexPkg)) {
                        oldApexPkg.isActiveApex = false;
                        mAllPackagesCache.add(newApexPkg);
@@ -264,7 +291,7 @@ class ApexPackageInfo {
            ApexInfo ai = result.apexInfo;

            final PackageInfo packageInfo = PackageInfoWithoutStateUtils.generate(
                    result.parsedPackage, ai, flags);
                    result.pkg, ai, flags);
            if (packageInfo == null) {
                throw new IllegalStateException("Unable to generate package info: "
                        + ai.modulePath);
+12 −2
Original line number Diff line number Diff line
@@ -185,9 +185,19 @@ final class InitAppsHelper {
    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
    private List<ApexManager.ScanResult> scanApexPackagesTraced(PackageParser2 packageParser) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackages");

        try {
            return mApexPackageInfo.scanApexPackages(
            final List<ApexManager.ScanResult> apexScanResults;
            if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
                apexScanResults = mInstallPackageHelper.scanApexPackages(
                        mApexManager.getAllApexInfos(), mSystemParseFlags, mSystemScanFlags,
                        packageParser, mExecutorService);
                mApexPackageInfo.notifyScanResult(apexScanResults);
            } else {
                apexScanResults = mApexPackageInfo.scanApexPackages(
                        mApexManager.getAllApexInfos(), packageParser, mExecutorService);
            }
            return apexScanResults;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
+68 −2
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.POST_INSTALL;
import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS;
import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY;
import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
@@ -92,6 +93,7 @@ import static com.android.server.pm.SharedUidMigration.BEST_EFFORT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.apex.ApexInfo;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.backup.IBackupManager;
@@ -870,7 +872,17 @@ final class InstallPackageHelper {
                                + " got: " + apexes.length);
            }
            try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
                mApexManager.installPackage(apexes[0], packageParser, mPm.mApexPackageInfo);
                ApexInfo apexInfo = mApexManager.installPackage(
                        apexes[0], packageParser, mPm.mApexPackageInfo);
                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
                    ParsedPackage parsedPackage = packageParser.parsePackage(
                            new File(apexInfo.modulePath), 0, /* useCaches= */ false);
                    scanSystemPackageLI(parsedPackage, 0, SCAN_AS_APEX, null);
                    mPm.mApexPackageInfo.notifyPackageInstalled(
                            apexInfo, parsedPackage.hideAsFinal());
                } else {
                    mPm.mApexPackageInfo.notifyPackageInstalled(apexInfo, packageParser);
                }
            }
        } catch (PackageManagerException e) {
            request.mInstallResult.setError("APEX installation failed", e);
@@ -3378,6 +3390,56 @@ final class InstallPackageHelper {
        }
    }

    public List<ApexManager.ScanResult> scanApexPackages(ApexInfo[] allPackages, int parseFlags,
            int scanFlags, PackageParser2 packageParser, ExecutorService executorService) {
        if (allPackages == null) {
            return Collections.EMPTY_LIST;
        }

        ParallelPackageParser parallelPackageParser =
                new ParallelPackageParser(packageParser, executorService);

        // Submit files for parsing in parallel
        ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>();
        for (ApexInfo ai : allPackages) {
            File apexFile = new File(ai.modulePath);
            parallelPackageParser.submit(apexFile, parseFlags);
            parsingApexInfo.put(apexFile, ai);
        }

        // Process results one by one
        List<ApexManager.ScanResult> results = new ArrayList<>(parsingApexInfo.size());
        for (int i = 0; i < parsingApexInfo.size(); i++) {
            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
            Throwable throwable = parseResult.throwable;
            ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
            int newScanFlags = scanFlags | SCAN_AS_APEX;

            if (throwable == null) {
                try {
                    scanSystemPackageLI(parseResult.parsedPackage, parseFlags, newScanFlags, null);
                    AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal();
                    results.add(new ApexManager.ScanResult(ai, pkg, pkg.getPackageName()));
                } catch (PackageManagerException e) {
                    throw new IllegalStateException("Failed to scan: " + ai.modulePath, e);
                }
            } else if (throwable instanceof PackageManagerException) {
                final PackageManagerException e = (PackageManagerException) throwable;
                // Skip parsing non-coreApp apex file if system is in minimal boot state.
                if (e.error == PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED) {
                    Slog.w(TAG, "Scan apex failed, not a coreApp:" + ai.modulePath);
                    continue;
                }
                throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
            } else {
                throw new IllegalStateException("Unexpected exception occurred while parsing "
                        + ai.modulePath, throwable);
            }
        }

        return results;
    }

    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
    public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
            int scanFlags, PackageParser2 packageParser,
@@ -4079,8 +4141,12 @@ final class InstallPackageHelper {
        // after OTA.
        final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
        final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
        // It is allowed to install a new APEX with the same name. But there shouldn't be
        // conflicting names between APK and APEX.
        final boolean installApex = (scanFlags & SCAN_AS_APEX) != 0;
        if ((isUserInstall || isFirstBootOrUpgrade)
                && mPm.mApexPackageInfo.isApexPackage(pkg.getPackageName())) {
                && mPm.mApexPackageInfo.isApexPackage(pkg.getPackageName())
                && !installApex) {
            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
                    pkg.getPackageName()
                            + " is an APEX package and can't be installed as an APK.");
+1 −0
Original line number Diff line number Diff line
@@ -375,6 +375,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
    static final int SCAN_AS_ODM = 1 << 22;
    static final int SCAN_AS_APK_IN_APEX = 1 << 23;
    static final int SCAN_AS_FACTORY = 1 << 24;
    static final int SCAN_AS_APEX = 1 << 25;

    @IntDef(flag = true, prefix = { "SCAN_" }, value = {
            SCAN_NO_DEX,
Loading