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

Commit 065aac1a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support for multi-package apex sessions."

parents a7b0f939 015f9351
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -1125,6 +1125,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements


        public void onStagedSessionChanged(PackageInstallerSession session) {
        public void onStagedSessionChanged(PackageInstallerSession session) {
            writeSessionsAsync();
            writeSessionsAsync();
            // TODO(b/118865310): don't send broadcast if system is not ready.
            mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
            mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
        }
        }


+23 −68
Original line number Original line Diff line number Diff line
@@ -992,8 +992,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {


        // Read transfers from the original owner stay open, but as the session's data
        // Read transfers from the original owner stay open, but as the session's data
        // cannot be modified anymore, there is no leak of information. For staged sessions,
        // cannot be modified anymore, there is no leak of information. For staged sessions,
        // further validation may be performed by the staging manager.
        // further validation is performed by the staging manager.
        if (!params.isMultiPackage) {
        if (!params.isMultiPackage) {
            if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
                // For APEX, validation is done by StagingManager post-commit.
                return;
            }
            final PackageInfo pkgInfo = mPm.getPackageInfo(
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES
                    params.appPackageName, PackageManager.GET_SIGNATURES
                            | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
                            | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1001,16 +1005,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            resolveStageDirLocked();
            resolveStageDirLocked();


            try {
            try {
                if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
                    // TODO(b/118865310): Remove this when APEX validation is done via
                    //                    StagingManager.
                    validateApexInstallLocked(pkgInfo);
                } else {
                    // Verify that stage looks sane with respect to existing application.
                    // This currently only ensures packageName, versionCode, and certificate
                    // consistency.
                validateApkInstallLocked(pkgInfo);
                validateApkInstallLocked(pkgInfo);
                }
            } catch (PackageManagerException e) {
            } catch (PackageManagerException e) {
                throw e;
                throw e;
            } catch (Throwable e) {
            } catch (Throwable e) {
@@ -1301,54 +1296,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                (params.installFlags & PackageManager.DONT_KILL_APP) != 0;
                (params.installFlags & PackageManager.DONT_KILL_APP) != 0;
    }
    }


    @GuardedBy("mLock")
    private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo)
            throws PackageManagerException {
        mResolvedStagedFiles.clear();
        mResolvedInheritedFiles.clear();

        try {
            resolveStageDirLocked();
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
                "Failed to resolve stage location", e);
        }

        final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
        if (ArrayUtils.isEmpty(addedFiles)) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
        }

        if (addedFiles.length > 1) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                "Only one APEX file at a time might be installed");
        }
        File addedFile = addedFiles[0];
        final ApkLite apk;
        try {
            apk = PackageParser.parseApkLite(
                addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }

        mPackageName = apk.packageName;
        mVersionCode = apk.getLongVersionCode();
        mSigningDetails = apk.signingDetails;
        mResolvedBaseFile = addedFile;

        // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
        // because we currently do not verify that signatures are consistent with the previously
        // installed version in that case.
        //
        // When that happens, this hack can be reverted and we can rely on APEXd to map between
        // APEX files and their package names instead of parsing it out of the AndroidManifest
        // such as here.
        if (params.appPackageName == null) {
            params.appPackageName = mPackageName;
        }
    }

    /**
    /**
     * Validate install by confirming that all application packages are have
     * Validate install by confirming that all application packages are have
     * consistent package name, version code, and signing certificates.
     * consistent package name, version code, and signing certificates.
@@ -1911,22 +1858,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }
    }


    @Override
    @Override
    public void addChildSessionId(int sessionId) {
    public void addChildSessionId(int childSessionId) {
        final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
        final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
        if (session == null) {
        if (childSession == null) {
            throw new RemoteException("Unable to add child.",
            throw new RemoteException("Unable to add child.",
                    new PackageManagerException("Child session " + sessionId + " does not exist"),
                    new PackageManagerException("Child session " + childSessionId
                            + " does not exist"),
                    false, true).rethrowAsRuntimeException();
        }
        // Session groups must be consistent wrt to isStaged parameter. Non-staging session
        // cannot be grouped with staging sessions.
        if (this.params.isStaged ^ childSession.params.isStaged) {
            throw new RemoteException("Unable to add child.",
                    new PackageManagerException("Child session " + childSessionId
                            + " and parent session " + this.sessionId + " do not have consistent"
                            + " staging session settings."),
                    false, true).rethrowAsRuntimeException();
                    false, true).rethrowAsRuntimeException();
        }
        }
        synchronized (mLock) {
        synchronized (mLock) {
            final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
            final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId);
            if (indexOfSession >= 0) {
            if (indexOfSession >= 0) {
                return;
                return;
            }
            }
            session.setParentSessionId(this.sessionId);
            childSession.setParentSessionId(this.sessionId);
            // TODO: sanity check, if parent session is staged then child session should be
            addChildSessionIdInternal(childSessionId);
            //       marked as staged.
            addChildSessionIdInternal(sessionId);
        }
        }
    }
    }


+104 −26
Original line number Original line Diff line number Diff line
@@ -41,7 +41,9 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BackgroundThread;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.List;
import java.util.stream.Collectors;


/**
/**
 * This class handles staged install sessions, i.e. install sessions that require packages to
 * This class handles staged install sessions, i.e. install sessions that require packages to
@@ -126,12 +128,24 @@ public class StagingManager {
        return false;
        return false;
    }
    }


    private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
    private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
                                                      List<PackageInstallerSession> childSessions,
                                                      ApexInfoList apexInfoList) {
        return sendSubmitStagedSessionRequest(
                session.sessionId,
                childSessions != null
                        ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
                        new int[]{},
                apexInfoList);
    }

    private static boolean sendSubmitStagedSessionRequest(
            int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) {
        final IApexService apex = IApexService.Stub.asInterface(
        final IApexService apex = IApexService.Stub.asInterface(
                ServiceManager.getService("apexservice"));
                ServiceManager.getService("apexservice"));
        boolean success;
        boolean success;
        try {
        try {
            success = apex.submitStagedSession(sessionId, new int[0], apexInfoList);
            success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList);
        } catch (RemoteException re) {
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact apexservice", re);
            Slog.e(TAG, "Unable to contact apexservice", re);
            return false;
            return false;
@@ -139,22 +153,40 @@ public class StagingManager {
        return success;
        return success;
    }
    }


    private static boolean isApexSession(@NonNull PackageInstallerSession session) {
        return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
    }

    private void preRebootVerification(@NonNull PackageInstallerSession session) {
    private void preRebootVerification(@NonNull PackageInstallerSession session) {
        boolean success = true;
        boolean success = true;
        if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {


        final ApexInfoList apexInfoList = new ApexInfoList();
        final ApexInfoList apexInfoList = new ApexInfoList();
        // APEX checks. For single-package sessions, check if they contain an APEX. For
        // multi-package sessions, find all the child sessions that contain an APEX.
        if (!session.isMultiPackage()
                && isApexSession(session)) {
            success = submitSessionToApexService(session, null, apexInfoList);
        } else if (session.isMultiPackage()) {
            List<PackageInstallerSession> childSessions =
                    Arrays.stream(session.getChildSessionIds())
                            // Retrieve cached sessions matching ids.
                            .mapToObj(i -> mStagedSessions.get(i))
                            // Filter only the ones containing APEX.
                            .filter(childSession -> isApexSession(childSession))
                            .collect(Collectors.toList());
            if (!childSessions.isEmpty()) {
                success = submitSessionToApexService(session, childSessions, apexInfoList);
            } // else this is a staged multi-package session with no APEX files.
        }


            if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
        if (success && (apexInfoList.apexInfos.length > 0)) {
                success = false;
            } else {
            // For APEXes, we validate the signature here before we mark the session as ready,
            // For APEXes, we validate the signature here before we mark the session as ready,
            // so we fail the session early if there is a signature mismatch. For APKs, the
            // so we fail the session early if there is a signature mismatch. For APKs, the
            // signature verification will be done by the package manager at the point at which
            // signature verification will be done by the package manager at the point at which
            // it applies the staged install.
            // it applies the staged install.
            //
            //
                // TODO: Decide whether we want to fail fast by detecting signature mismatches right
            // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
                // away.
            // right away.
            for (ApexInfo apexPackage : apexInfoList.apexInfos) {
            for (ApexInfo apexPackage : apexInfoList.apexInfos) {
                if (!validateApexSignatureLocked(apexPackage.packagePath,
                if (!validateApexSignatureLocked(apexPackage.packagePath,
                        apexPackage.packageName)) {
                        apexPackage.packageName)) {
@@ -163,7 +195,7 @@ public class StagingManager {
                }
                }
            }
            }
        }
        }
        }

        if (success) {
        if (success) {
            session.setStagedSessionReady();
            session.setStagedSessionReady();
        } else {
        } else {
@@ -206,15 +238,59 @@ public class StagingManager {
        }
        }
    }
    }


    void abortSession(@NonNull PackageInstallerSession sessionInfo) {
    void abortSession(@NonNull PackageInstallerSession session) {
        updateStoredSession(sessionInfo);
        synchronized (mStagedSessions) {
        synchronized (mStagedSessions) {
            mStagedSessions.remove(sessionInfo.sessionId);
            updateStoredSession(session);
            mStagedSessions.remove(session.sessionId);
        }
        }
    }
    }


    @GuardedBy("mStagedSessions")
    private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) {
        // This method assumes that the argument is either a parent session of a multi-package
        // i.e. isMultiPackage() returns true, or that it is a child session, i.e.
        // hasParentSessionId() returns true.
        if (session.isMultiPackage()) {
            // Parent session of a multi-package group. Check that we restored all the children.
            for (int childSession : session.getChildSessionIds()) {
                if (mStagedSessions.get(childSession) == null) {
                    return false;
                }
            }
            return true;
        }
        if (session.hasParentSessionId()) {
            PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId());
            if (parent == null) {
                return false;
            }
            return isMultiPackageSessionComplete(parent);
        }
        Slog.wtf(TAG, "Attempting to restore an invalid multi-package session.");
        return false;
    }

    void restoreSession(@NonNull PackageInstallerSession session) {
    void restoreSession(@NonNull PackageInstallerSession session) {
        updateStoredSession(session);
        PackageInstallerSession sessionToResume = session;
        synchronized (mStagedSessions) {
            mStagedSessions.append(session.sessionId, session);
            // For multi-package sessions, we don't know in which order they will be restored. We
            // need to wait until we have restored all the session in a group before restoring them.
            if (session.isMultiPackage() || session.hasParentSessionId()) {
                if (!isMultiPackageSessionComplete(session)) {
                    // Still haven't recovered all sessions of the group, return.
                    return;
                }
                // Group recovered, find the parent if necessary and resume the installation.
                if (session.hasParentSessionId()) {
                    sessionToResume = mStagedSessions.get(session.getParentSessionId());
                }
            }
        }
        checkStateAndResume(sessionToResume);
    }

    private void checkStateAndResume(@NonNull PackageInstallerSession session) {
        // Check the state of the session and decide what to do next.
        // Check the state of the session and decide what to do next.
        if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
        if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) {
            // Final states, nothing to do.
            // Final states, nothing to do.
@@ -227,6 +303,8 @@ public class StagingManager {
        } else {
        } else {
            // Session had already being marked ready. Start the checks to verify if there is any
            // Session had already being marked ready. Start the checks to verify if there is any
            // follow-up work.
            // follow-up work.
            // TODO(b/118865310): should this be synchronous to ensure it completes before
            //                    systemReady() finishes?
            mBgHandler.post(() -> resumeSession(session));
            mBgHandler.post(() -> resumeSession(session));
        }
        }
    }
    }