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

Commit 2e8dffcb authored by Dario Freni's avatar Dario Freni
Browse files

Factor out calls to apexservice in a class.

The ApexManager class provides a cleaner interface to the apex service,
as well as providing caching for active packages, which can't change on
a running system. The cache is populated at boot time.

This CL will also cause PackageManager to stop reporting APEX packages
on devices that ship with flattened APEXs.

Test: atest apex_e2e_tests; used small app to verify API calls still
work; checked output of dumpsys.
Test: checked that on marlin (target with flatten APEX) no APEXs are
reported and no crashes are experienced at boot.
Fix: 123052859
Fix: 122638509
Fix: 124299505
Bug: 122952270
Change-Id: Iefe4fb42e455a7479ff47eb776d3492de8395469
parent 1466e4a4
Loading
Loading
Loading
Loading
+221 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.s
 */

package com.android.server.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;

import com.android.internal.util.IndentingPrintWriter;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ApexManager class handles communications with the apex service to perform operation and queries,
 * as well as providing caching to avoid unnecessary calls to the service.
 */
class ApexManager {
    static final String TAG = "ApexManager";
    private final IApexService mApexService;
    private final Map<String, PackageInfo> mActivePackagesCache;

    ApexManager() {
        mApexService = IApexService.Stub.asInterface(
            ServiceManager.getService("apexservice"));
        mActivePackagesCache = populateActivePackagesCache();
    }

    @NonNull
    private Map<String, PackageInfo> populateActivePackagesCache() {
        try {
            List<PackageInfo> list = new ArrayList<>();
            final ApexInfo[] activePkgs = mApexService.getActivePackages();
            for (ApexInfo ai : activePkgs) {
                // 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.packagePath)).isDirectory()) {
                    break;
                }
                try {
                    list.add(PackageParser.generatePackageInfoFromApex(
                            new File(ai.packagePath), true /* collect certs */));
                } catch (PackageParserException pe) {
                    throw new IllegalStateException("Unable to parse: " + ai, pe);
                }
            }
            return list.stream().collect(Collectors.toMap(p -> p.packageName, Function.identity()));
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
            throw new RuntimeException(re);
        }
    }

    /**
     * Retrieves information about an active APEX package.
     *
     * @param packageName the package name to look for. Note that this is the package name reported
     *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
     *                    differ from the one reported in the APEX manifest (i.e.
     *                    apex_manifest.json).
     * @return a PackageInfo object with the information about the package, or null if the package
     *         is not found.
     */
    @Nullable PackageInfo getActivePackage(String packageName) {
        return mActivePackagesCache.get(packageName);
    }

    /**
     * Retrieves information about all active APEX packages.
     *
     * @return a Collection of PackageInfo object, each one containing information about a different
     *         active package.
     */
    Collection<PackageInfo> getActivePackages() {
        return mActivePackagesCache.values();
    }

    /**
     * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
     * track the different states of a session.
     *
     * @param sessionId the identifier of the session.
     * @return an ApexSessionInfo object, or null if the session is not known.
     */
    @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
        try {
            ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
            if (apexSessionInfo.isUnknown) {
                return null;
            }
            return apexSessionInfo;
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            throw new RuntimeException(re);
        }
    }

    /**
     * Submit a staged session to apex service. This causes the apex service to perform some initial
     * verification and accept or reject the session. Submitting a session successfully is not
     * enough for it to be activated at the next boot, the caller needs to call
     * {@link #markStagedSessionReady(int)}.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
     * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
     *                        an array of identifiers of all the child sessions. Otherwise it should
     *                        be an empty array.
     * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
     *                     and will be filled with a list of {@link ApexInfo} objects, each of which
     *                     contains metadata about one of the packages being submitted as part of
     *                     the session.
     * @return whether the submission of the session was successful.
     */
    boolean submitStagedSession(
            int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
        try {
            return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            throw new RuntimeException(re);
        }
    }

    /**
     * Mark a staged session previously submitted using {@cde submitStagedSession} as ready to be
     * applied at next reboot.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
     * @return true upon success, false if the session is unknown.
     */
    boolean markStagedSessionReady(int sessionId) {
        try {
            return mApexService.markStagedSessionReady(sessionId);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            throw new RuntimeException(re);
        }
    }

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
     * @param pw the {@link PrintWriter} object to send information to.
     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
     *                    information about that specific package will be dumped.
     */
    void dump(PrintWriter pw, @Nullable String packageName) {
        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
        ipw.println();
        ipw.println("Active APEX packages:");
        ipw.increaseIndent();
        try {
            populateActivePackagesCache();
            for (PackageInfo pi : mActivePackagesCache.values()) {
                if (packageName != null && !packageName.equals(pi.packageName)) {
                    continue;
                }
                ipw.println(pi.packageName);
                ipw.increaseIndent();
                ipw.println("Version: " + pi.versionCode);
                ipw.println("Path: " + pi.applicationInfo.sourceDir);
                ipw.decreaseIndent();
            }
            ipw.decreaseIndent();
            ipw.println();
            ipw.println("APEX session state:");
            ipw.increaseIndent();
            final ApexSessionInfo[] sessions = mApexService.getSessions();
            for (ApexSessionInfo si : sessions) {
                ipw.println("Session ID: " + Integer.toString(si.sessionId));
                ipw.increaseIndent();
                if (si.isUnknown) {
                    ipw.println("State: UNKNOWN");
                } else if (si.isVerified) {
                    ipw.println("State: VERIFIED");
                } else if (si.isStaged) {
                    ipw.println("State: STAGED");
                } else if (si.isActivated) {
                    ipw.println("State: ACTIVATED");
                } else if (si.isActivationPendingRetry) {
                    ipw.println("State: ACTIVATION PENDING RETRY");
                } else if (si.isActivationFailed) {
                    ipw.println("State: ACTIVATION FAILED");
                }
                ipw.decreaseIndent();
            }
            ipw.decreaseIndent();
        } catch (RemoteException e) {
            ipw.println("Couldn't communicate with apexd.");
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -186,7 +186,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        }
    };

    public PackageInstallerService(Context context, PackageManagerService pm) {
    public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
        mContext = context;
        mPm = pm;
        mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
@@ -204,7 +204,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
        mSessionsDir.mkdirs();

        mStagingManager = new StagingManager(pm, this);
        mStagingManager = new StagingManager(pm, this, am);
    }

    private void setBootCompleted()  {
+7 −91
Original line number Diff line number Diff line
@@ -119,9 +119,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppDetailsActivity;
@@ -733,10 +730,10 @@ public class PackageManagerService extends IPackageManager.Stub
    @GuardedBy("mPackages")
    final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
    private PackageManager mPackageManager;
    private final ModuleInfoProvider mModuleInfoProvider;
    private final ApexManager mApexManager;
    class PackageParserCallback implements PackageParser.Callback {
        @Override public final boolean hasFeature(String feature) {
            return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -3074,7 +3071,8 @@ public class PackageManagerService extends IPackageManager.Stub
                }
            }
            mInstallerService = new PackageInstallerService(context, this);
            mApexManager = new ApexManager();
            mInstallerService = new PackageInstallerService(context, this, mApexManager);
            final Pair<ComponentName, String> instantAppResolverComponent =
                    getInstantAppResolverLPr();
            if (instantAppResolverComponent != null) {
@@ -3934,27 +3932,7 @@ public class PackageManagerService extends IPackageManager.Stub
            }
            //
            if (!matchFactoryOnly && (flags & MATCH_APEX) != 0) {
                //TODO(b/123052859) Don't do file operations every time there is a query.
                final IApexService apex = IApexService.Stub.asInterface(
                        ServiceManager.getService("apexservice"));
                if (apex != null) {
                    try {
                        final ApexInfo activePkg = apex.getActivePackage(packageName);
                        if (activePkg != null && !TextUtils.isEmpty(activePkg.packagePath)) {
                            try {
                                return PackageParser.generatePackageInfoFromApex(
                                        new File(activePkg.packagePath), true /* collect certs */);
                            } catch (PackageParserException pe) {
                                Log.e(TAG, "Unable to parse package at "
                                        + activePkg.packagePath, pe);
                            }
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString());
                    }
                } else {
                    Log.e(TAG, "Unable to connect to apexservice for querying packages.");
                }
                return mApexManager.getActivePackage(packageName);
            }
        }
        return null;
@@ -7851,25 +7829,7 @@ public class PackageManagerService extends IPackageManager.Stub
            if (listApex) {
                // TODO(b/119767311): include uninstalled/inactive APEX if
                //  MATCH_UNINSTALLED_PACKAGES is set.
                final IApexService apex = IApexService.Stub.asInterface(
                        ServiceManager.getService("apexservice"));
                if (apex != null) {
                    try {
                        final ApexInfo[] activePkgs = apex.getActivePackages();
                        for (ApexInfo ai : activePkgs) {
                            try {
                                 list.add(PackageParser.generatePackageInfoFromApex(
                                         new File(ai.packagePath), true /* collect certs */));
                            } catch (PackageParserException pe) {
                                 throw new IllegalStateException("Unable to parse: " + ai, pe);
                            }
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "Unable to retrieve packages from apexservice: " + e.toString());
                    }
                } else {
                    Log.e(TAG, "Unable to connect to apexservice for querying packages.");
                }
                list.addAll(mApexManager.getActivePackages());
            }
            return new ParceledListSlice<>(list);
        }
@@ -21319,51 +21279,7 @@ public class PackageManagerService extends IPackageManager.Stub
        }
        if (!checkin && dumpState.isDumping(DumpState.DUMP_APEX)) {
            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
            ipw.println();
            ipw.println("Active APEX packages:");
            ipw.increaseIndent();
            final IApexService apex = IApexService.Stub.asInterface(
                    ServiceManager.getService("apexservice"));
            try {
                final ApexInfo[] activeApexes = apex.getActivePackages();
                for (ApexInfo ai : activeApexes) {
                    if (packageName != null && !packageName.equals(ai.packageName)) {
                        continue;
                    }
                    ipw.println(ai.packageName);
                    ipw.increaseIndent();
                    ipw.println("Version: " + Long.toString(ai.versionCode));
                    ipw.println("Path: " + ai.packagePath);
                    ipw.decreaseIndent();
                }
                ipw.decreaseIndent();
                ipw.println();
                ipw.println("APEX session state:");
                ipw.increaseIndent();
                final ApexSessionInfo[] sessions = apex.getSessions();
                for (ApexSessionInfo si : sessions) {
                    ipw.println("Session ID: " + Integer.toString(si.sessionId));
                    ipw.increaseIndent();
                    if (si.isUnknown) {
                        ipw.println("State: UNKNOWN");
                    } else if (si.isVerified) {
                        ipw.println("State: VERIFIED");
                    } else if (si.isStaged) {
                        ipw.println("State: STAGED");
                    } else if (si.isActivated) {
                        ipw.println("State: ACTIVATED");
                    } else if (si.isActivationPendingRetry) {
                        ipw.println("State: ACTIVATION PENDING RETRY");
                    } else if (si.isActivationFailed) {
                        ipw.println("State: ACTIVATION FAILED");
                    }
                    ipw.decreaseIndent();
                }
                ipw.decreaseIndent();
            } catch (RemoteException e) {
                ipw.println("Couldn't communicate with apexd.");
            }
            mApexManager.dump(pw, packageName);
        }
    }
+23 −60
Original line number Diff line number Diff line
@@ -20,12 +20,12 @@ import android.annotation.NonNull;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -41,7 +41,6 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
@@ -68,14 +67,16 @@ public class StagingManager {

    private final PackageInstallerService mPi;
    private final PackageManagerService mPm;
    private final ApexManager mApexManager;
    private final Handler mBgHandler;

    @GuardedBy("mStagedSessions")
    private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();

    StagingManager(PackageManagerService pm, PackageInstallerService pi) {
    StagingManager(PackageManagerService pm, PackageInstallerService pi, ApexManager am) {
        mPm = pm;
        mPi = pi;
        mApexManager = am;
        mBgHandler = BackgroundThread.getHandler();
    }

@@ -100,7 +101,7 @@ public class StagingManager {
        return new ParceledListSlice<>(result);
    }

    private static boolean validateApexSignature(String apexPath, String packageName) {
    private boolean validateApexSignature(String apexPath, String packageName) {
        final SigningDetails signingDetails;
        try {
            signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
@@ -109,17 +110,9 @@ public class StagingManager {
            return false;
        }

        final IApexService apex = IApexService.Stub.asInterface(
                ServiceManager.getService("apexservice"));
        final ApexInfo apexInfo;
        try {
            apexInfo = apex.getActivePackage(packageName);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact APEXD", re);
            return false;
        }
        final PackageInfo packageInfo = mApexManager.getActivePackage(packageName);

        if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) {
        if (packageInfo == null) {
            // TODO: What is the right thing to do here ? This implies there's no active package
            // with the given name. This should never be the case in production (where we only
            // accept updates to existing APEXes) but may be required for testing.
@@ -129,9 +122,10 @@ public class StagingManager {
        final SigningDetails existingSigningDetails;
        try {
            existingSigningDetails = ApkSignatureVerifier.verify(
                apexInfo.packagePath, SignatureSchemeVersion.JAR);
                packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
        } catch (PackageParserException e) {
            Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e);
            Slog.e(TAG, "Unable to parse APEX package: "
                    + packageInfo.applicationInfo.sourceDir, e);
            return false;
        }

@@ -143,10 +137,10 @@ public class StagingManager {
        return false;
    }

    private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
    private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
                                               List<PackageInstallerSession> childSessions,
                                               ApexInfoList apexInfoList) {
        return sendSubmitStagedSessionRequest(
        return mApexManager.submitStagedSession(
                session.sessionId,
                childSessions != null
                        ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
@@ -154,33 +148,6 @@ public class StagingManager {
                apexInfoList);
    }

    private static boolean sendSubmitStagedSessionRequest(
            int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
        final IApexService apex = IApexService.Stub.asInterface(
                ServiceManager.getService("apexservice"));
        boolean success;
        try {
            success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            return false;
        }
        return success;
    }

    private static boolean sendMarkStagedSessionReadyRequest(int sessionId) {
        final IApexService apex = IApexService.Stub.asInterface(
                ServiceManager.getService("apexservice"));
        boolean success;
        try {
            success = apex.markStagedSessionReady(sessionId);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            return false;
        }
        return success;
    }

    private static boolean isApexSession(@NonNull PackageInstallerSession session) {
        return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
    }
@@ -260,7 +227,7 @@ public class StagingManager {
        }

        session.setStagedSessionReady();
        if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
        if (!mApexManager.markStagedSessionReady(session.sessionId)) {
            session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                            "APEX staging failed, check logcat messages from apexd for more "
                            + "details.");
@@ -284,16 +251,12 @@ public class StagingManager {

    private void resumeSession(@NonNull PackageInstallerSession session) {
        if (sessionContainsApex(session)) {
            // Check with apexservice whether the apex
            // packages have been activated.
            final IApexService apex = IApexService.Stub.asInterface(
                    ServiceManager.getService("apexservice"));
            ApexSessionInfo apexSessionInfo;
            try {
                apexSessionInfo = apex.getStagedSessionInfo(session.sessionId);
            } catch (RemoteException re) {
                Slog.e(TAG, "Unable to contact apexservice", re);
                // TODO should we retry here? Mark the session as failed?
            // Check with apexservice whether the apex packages have been activated.
            ApexSessionInfo apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId);
            if (apexSessionInfo == null) {
                session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                        "apexd did not know anything about a staged session supposed to be"
                        + "activated");
                return;
            }
            if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
@@ -323,7 +286,7 @@ public class StagingManager {
        // The APEX part of the session is activated, proceed with the installation of APKs.
        if (!installApksInSession(session)) {
            session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                    "APEX activation failed. Check logcat messages from apexd for "
                    "Staged installation of APKs failed. Check logcat messages for"
                        + "more information.");
            return;
        }