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

Commit 7da35883 authored by Mohammad Samiul Islam's avatar Mohammad Samiul Islam Committed by Android (Google) Code Review
Browse files

Merge "Allow staging multiple session with non-overlapping packages (apk-only)"

parents f2e1609f da00497f
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -11524,7 +11524,8 @@ package android.content.pm {
  public class PackageInstaller {
    method public void abandonSession(int);
    method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
    method @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
    method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
    method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
    method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions();
    method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions();
    method @Nullable public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
@@ -11609,11 +11610,13 @@ package android.content.pm {
    method @NonNull public String getStagedSessionErrorMessage();
    method public long getUpdatedMillis();
    method @NonNull public android.os.UserHandle getUser();
    method public boolean hasParentSessionId();
    method public boolean isActive();
    method public boolean isCommitted();
    method public boolean isMultiPackage();
    method public boolean isSealed();
    method public boolean isStaged();
    method public boolean isStagedSessionActive();
    method public boolean isStagedSessionApplied();
    method public boolean isStagedSessionFailed();
    method public boolean isStagedSessionReady();
+52 −26
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * Offers the ability to install, upgrade, and remove applications on the
@@ -479,35 +480,30 @@ public class PackageInstaller {
    }

    /**
     * Returns an active staged session, or {@code null} if there is none.
     * Returns first active staged session, or {@code null} if there is none.
     *
     * <p>Staged session is active iff:
     * <ul>
     *     <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and
     *     <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code
     *     false}, and
     *     <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is {@code false}.
     * </ul>
     * <p>For more information on what sessions are considered active see
     * {@link SessionInfo#isStagedSessionActive()}.
     *
     * <p>In case of a multi-apk session, reasoning above is applied to the parent session, since
     * that is the one that should been {@link Session#commit committed}.
     * @deprecated Use {@link #getActiveStagedSessions} as there can be more than one active staged
     * session
     */
    @Deprecated
    public @Nullable SessionInfo getActiveStagedSession() {
        final List<SessionInfo> stagedSessions = getStagedSessions();
        for (SessionInfo s : stagedSessions) {
            if (s.isStagedSessionApplied() || s.isStagedSessionFailed()) {
                // Finalized session.
                continue;
            }
            if (s.getParentSessionId() != SessionInfo.INVALID_ID) {
                // Child session.
                continue;
        List<SessionInfo> activeSessions = getActiveStagedSessions();
        return activeSessions.isEmpty() ? null : activeSessions.get(0);
    }
            if (s.isCommitted()) {
                return s;
            }
        }
        return null;

    /**
     * Returns list of active staged sessions. Returns empty list if there is none.
     *
     * <p>For more information on what sessions are considered active see
     *      * {@link SessionInfo#isStagedSessionActive()}.
     */
    public @NonNull List<SessionInfo> getActiveStagedSessions() {
        return getStagedSessions().stream()
                .filter(s -> s.isStagedSessionActive())
                .collect(Collectors.toList());
    }

    /**
@@ -2227,12 +2223,35 @@ public class PackageInstaller {
        }

        /**
         * Returns true if this session is a staged session which will be applied at next reboot.
         * Returns true if this session is a staged session.
         */
        public boolean isStaged() {
            return isStaged;
        }

        /**
         * Returns {@code true} if this session is an active staged session.
         *
         * We consider a session active if it has been committed and it is either pending
         * verification, or will be applied at next reboot.
         *
         * <p>Staged session is active iff:
         * <ul>
         *     <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and
         *     <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code
         *     false}, and
         *     <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is
         *     {@code false}.
         * </ul>
         *
         * <p>In case of a multi-package session, reasoning above is applied to the parent session,
         * since that is the one that should have been {@link Session#commit committed}.
         */
        public boolean isStagedSessionActive() {
            return isStaged && isCommitted && !isStagedSessionApplied && !isStagedSessionFailed
                    && !hasParentSessionId();
        }

        /**
         * Returns the parent multi-package session ID if this session belongs to one,
         * {@link #INVALID_ID} otherwise.
@@ -2241,6 +2260,13 @@ public class PackageInstaller {
            return parentSessionId;
        }

        /**
         * Returns true if session has a valid parent session, otherwise false.
         */
        public boolean hasParentSessionId() {
            return parentSessionId != INVALID_ID;
        }

        /**
         * Returns the set of session IDs that will be committed when this session is commited if
         * this session is a multi-package session.
+1 −1
Original line number Diff line number Diff line
@@ -1435,7 +1435,7 @@ public abstract class PackageManager {

    /**
     * Installation failed return code: a new staged session was attempted to be committed while
     * there is already one in-progress.
     * there is already one in-progress or new session has package that is already staged.
     *
     * @hide
     */
+30 −15
Original line number Diff line number Diff line
@@ -1095,19 +1095,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                assertMultiPackageConsistencyLocked(childSessions);
            }

            if (params.isStaged) {
                final PackageInstallerSession activeSession = mStagingManager.getActiveSession();
                final boolean anotherSessionAlreadyInProgress =
                        activeSession != null && sessionId != activeSession.sessionId
                                && mParentSessionId != activeSession.sessionId;
                if (anotherSessionAlreadyInProgress) {
                    throw new PackageManagerException(
                            PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
                            "There is already in-progress committed staged session "
                                    + activeSession.sessionId, null);
                }
            }

            // 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,
            // further validation is performed by the staging manager.
@@ -1130,6 +1117,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    throw new PackageManagerException(e);
                }
            }

            if (params.isStaged) {
                mStagingManager.checkNonOverlappingWithStagedSessions(this);
            }
        } catch (PackageManagerException e) {
            // Session is sealed but could not be verified, we need to destroy it.
            destroyInternal();
@@ -1453,7 +1444,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    /**
     * Validate apex install.
     * <p>
     * Sets {@link #mResolvedBaseFile} for RollbackManager to use.
     * Sets {@link #mResolvedBaseFile} for RollbackManager to use. Sets {@link #mPackageName} for
     * StagingManager to use.
     */
    @GuardedBy("mLock")
    private void validateApexInstallLocked()
@@ -1482,8 +1474,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        final File targetFile = new File(stageDir, targetName);
        resolveAndStageFile(addedFile, targetFile);

        mResolvedBaseFile = targetFile;

        // Populate package name of the apex session
        mPackageName = null;
        final ApkLite apk;
        try {
            apk = PackageParser.parseApkLite(
                    mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }

        if (mPackageName == null) {
            mPackageName = apk.packageName;
            mVersionCode = apk.getLongVersionCode();
        }
    }

    /**
@@ -1851,6 +1857,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * @return the package name of this session
     */
    String getPackageName() {
        synchronized (mLock) {
            return mPackageName;
        }
    }

    /**
     * @return the timestamp of when this session last changed state
     */
+82 −11
Original line number Diff line number Diff line
@@ -546,25 +546,95 @@ public class StagingManager {
        mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
    }

    @Nullable
    PackageInstallerSession getActiveSession() {
    /**
     * <p> Check if the session provided is non-overlapping with the active staged sessions.
     *
     * <p> A session is non-overlapping if it meets one of the following conditions: </p>
     * <ul>
     *     <li>It is a parent session</li>
     *     <li>It is already one of the active sessions</li>
     *     <li>Its package name is not same as any of the active sessions</li>
     * </ul>
     * @throws PackageManagerException if session fails the check
     */
    void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
            throws PackageManagerException {
        if (session.isMultiPackage()) {
            // We cannot say a parent session overlaps until we process its children
            return;
        }
        if (session.getPackageName() == null) {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
                    "Cannot stage session " + session.sessionId + " with package name null");
        }

        synchronized (mStagedSessions) {
            for (int i = 0; i < mStagedSessions.size(); i++) {
                final PackageInstallerSession session = mStagedSessions.valueAt(i);
                if (!session.isCommitted()) {
                final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
                if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
                    continue;
                }
                if (session.hasParentSessionId()) {
                    // Staging manager will finalize only parent session. Ignore child sessions
                    // picking the active.
                if (stagedSession.isMultiPackage()) {
                    // This active parent staged session is useless as it doesn't have a package
                    // name and the session we are checking is not a parent session either.
                    continue;
                }
                if (!session.isStagedSessionApplied() && !session.isStagedSessionFailed()) {
                    return session;

                // From here on, stagedSession is a non-parent active staged session

                // Check if stagedSession has an active parent session or not
                if (stagedSession.hasParentSessionId()) {
                    int parentId = stagedSession.getParentSessionId();
                    PackageInstallerSession parentSession = mStagedSessions.get(parentId);
                    if (parentSession == null || parentSession.isStagedAndInTerminalState()) {
                        // Parent session has been abandoned or terminated already
                        continue;
                    }
                }

                // Check if session is one of the active sessions
                if (session.sessionId == stagedSession.sessionId) {
                    Slog.w(TAG, "Session " + session.sessionId + " is already staged");
                    continue;
                }

                // If session is not among the active sessions, then it cannot have same package
                // name as any of the active sessions.
                if (session.getPackageName().equals(stagedSession.getPackageName())) {
                    throw new PackageManagerException(
                            PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
                            "Package: " + session.getPackageName() + " in session: "
                                    + session.sessionId + " has been staged already by session: "
                                    + stagedSession.sessionId, null);
                }

                // TODO(b/141843321): Add support for staging multiple sessions in apexd
                // Since apexd doesn't support multiple staged sessions yet, we have to careful how
                // we handle apex sessions. We want to allow a set of apex sessions under the same
                // parent to be staged when there is no previously staged apex sessions.
                if (isApexSession(session) && isApexSession(stagedSession)) {
                    // session is apex and it can co-exist with stagedSession only if they are from
                    // same parent
                    final boolean coExist;
                    if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) {
                        // Both single package apex sessions. Cannot co-exist.
                        coExist = false;
                    } else {
                        // At least one of the session has parent. Both must be from same parent.
                        coExist =
                                session.getParentSessionId() == stagedSession.getParentSessionId();
                    }
                    if (!coExist) {
                        throw new PackageManagerException(
                                PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
                                "Package: " + session.getPackageName() + " in session: "
                                        + session.sessionId + " cannot be staged as there is "
                                        + "already another apex staged session: "
                                        + stagedSession.sessionId, null);
                    }
                }
            }
        }
        return null;
    }

    void createSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -591,7 +661,8 @@ public class StagingManager {
            ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
            if (apexSession == null || isApexSessionFinalized(apexSession)) {
                Slog.w(TAG,
                        "Cannot abort session because it is not active or APEXD is not reachable");
                        "Cannot abort session " + session.sessionId
                                + " because it is not active or APEXD is not reachable");
                return;
            }
            mApexManager.abortActiveSession();