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

Commit 9541b091 authored by Rhed Jao's avatar Rhed Jao Committed by Philip P. Moltmann
Browse files

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
parent 93032f6f
Loading
Loading
Loading
Loading
+116 −107
Original line number Diff line number Diff line
@@ -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,77 +420,63 @@ 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);
        void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
                @NonNull ExecutorService executorService) {
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackagesTraced");
            try {
                synchronized (mLock) {
                    scanApexPackagesInternalLocked(packageParser, executorService);
                }
            }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }

        private void populatePackageNameToApexModuleNameIfNeeded() {
            synchronized (mLock) {
                if (mPackageNameToApexModuleName != null) {
                    return;
        }

        @GuardedBy("mLock")
        private void scanApexPackagesInternalLocked(PackageParser2 packageParser,
                ExecutorService executorService) {
            final ApexInfo[] allPkgs;
            try {
                mAllPackagesCache = new ArrayList<>();
                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);
                    }
                allPkgs = mApexService.getAllPackages();
            } catch (RemoteException re) {
                    Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
                Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
                throw new RuntimeException(re);
            }
            }
        }

        private void populateAllPackagesCacheIfNeeded() {
            synchronized (mLock) {
                if (mAllPackagesCache != null) {
            if (allPkgs.length == 0) {
                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 {
            ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>();
            ParallelPackageParser parallelPackageParser =
                    new ParallelPackageParser(packageParser, executorService);

            for (ApexInfo ai : allPkgs) {
                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);
                parallelPackageParser.submit(apexFile, flags);
                parsingApexInfo.put(apexFile, ai);
            }

                        final PackageInfo packageInfo =
                                PackageParser.generatePackageInfo(pkg, ai, flags);
            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);
                    }
                    mAllPackagesCache.add(packageInfo);
                    mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
                    if (ai.isActive) {
                        if (activePackagesSet.contains(packageInfo.packageName)) {
                            throw new IllegalStateException(
@@ -498,17 +493,19 @@ public abstract class ApexManager {
                        }
                        factoryPackagesSet.add(packageInfo.packageName);
                    }
                    }
                } catch (RemoteException re) {
                    Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
                    throw new RuntimeException(re);
                } 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
        }

+3 −1
Original line number Diff line number Diff line
@@ -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(
+23 −0
Original line number Diff line number Diff line
@@ -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();
    }