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

Commit 1d561ca0 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Allow staging multiple session with non-overlapping packages...

Merge "Allow staging multiple session with non-overlapping packages (apk-only)" am: 78200303 am: 65421fe4

Change-Id: I6e2bc58274906e87df98f2ad407365d903b73d7a
parents cb7711e9 65421fe4
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -11405,7 +11405,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);
@@ -11490,11 +11491,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
@@ -486,35 +487,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());
    }

    /**
@@ -2234,12 +2230,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.
@@ -2248,6 +2267,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
@@ -1424,7 +1424,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
@@ -1090,19 +1090,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.
@@ -1125,6 +1112,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();
@@ -1449,7 +1440,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()
@@ -1478,8 +1470,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();
        }
    }

    /**
@@ -1847,6 +1853,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
@@ -539,25 +539,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) {
@@ -584,7 +654,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();