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

Commit e9ea9cc7 authored by Narayan Kamath's avatar Narayan Kamath Committed by Android (Google) Code Review
Browse files

Merge "Staged Rollbacks Part 1: Pre reboot data copy."

parents aba2ef4a fcd4a04c
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -40,4 +40,11 @@ interface IRollbackManager {

    // Exposed for test purposes only.
    void expireRollbackForPackage(String packageName);

    // Used by the staging manager to notify the RollbackManager that a session is
    // being staged. In the case of multi-package sessions, the specified sessionId
    // is that of the parent session.
    //
    // NOTE: This call is synchronous.
    boolean notifyStagedSession(int sessionId);
}
+15 −1
Original line number Diff line number Diff line
@@ -68,6 +68,11 @@ public final class PackageRollbackInfo implements Parcelable {
    // NOTE: Not a part of the Parcelable representation of this object.
    private final ArrayList<RestoreInfo> mPendingRestores;

    /**
     * Whether this instance represents the PackageRollbackInfo for an APEX module.
     */
    private final boolean mIsApex;

    /**
     * Returns the name of the package to roll back from.
     */
@@ -115,19 +120,27 @@ public final class PackageRollbackInfo implements Parcelable {
        mPendingRestores.remove(ri);
    }

    /** @hide */
    public boolean isApex() {
        return mIsApex;
    }

    /** @hide */
    public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
            VersionedPackage packageRolledBackTo,
            @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores) {
            @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
            boolean isApex) {
        this.mVersionRolledBackFrom = packageRolledBackFrom;
        this.mVersionRolledBackTo = packageRolledBackTo;
        this.mPendingBackups = pendingBackups;
        this.mPendingRestores = pendingRestores;
        this.mIsApex = isApex;
    }

    private PackageRollbackInfo(Parcel in) {
        this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
        this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
        this.mIsApex = in.readBoolean();
        this.mPendingRestores = null;
        this.mPendingBackups = null;
    }
@@ -141,6 +154,7 @@ public final class PackageRollbackInfo implements Parcelable {
    public void writeToParcel(Parcel out, int flags) {
        mVersionRolledBackFrom.writeToParcel(out, flags);
        mVersionRolledBackTo.writeToParcel(out, flags);
        out.writeBoolean(mIsApex);
    }

    public static final Parcelable.Creator<PackageRollbackInfo> CREATOR =
+19 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ 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;
@@ -33,6 +34,7 @@ import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.content.rollback.IRollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -239,6 +241,23 @@ public class StagingManager {
            }
        }

        if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
            // If rollback is enabled for this session, we call through to the RollbackManager
            // with the list of sessions it must enable rollback for. Note that notifyStagedSession
            // is a synchronous operation.
            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
            try {
                // NOTE: To stay consistent with the non-staged install flow, we don't fail the
                // entire install if rollbacks can't be enabled.
                if (!rm.notifyStagedSession(session.sessionId)) {
                    Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
                }
            } catch (RemoteException re) {
                // Cannot happen, the rollback manager is in the same process.
            }
        }

        session.setStagedSessionReady();
        if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
            session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+8 −1
Original line number Diff line number Diff line
@@ -49,6 +49,12 @@ class RollbackData {
     */
    public Instant timestamp;

    /**
     * The session ID for the staged session if this rollback data represents a staged session,
     * {@code -1} otherwise.
     */
    public int stagedSessionId;

    /**
     * Whether this Rollback is currently in progress. This field is true from the point
     * we commit a {@code PackageInstaller} session containing these packages to the point the
@@ -57,8 +63,9 @@ class RollbackData {
    // NOTE: All accesses to this field are from the RollbackManager handler thread.
    public boolean inProgress = false;

    RollbackData(int rollbackId, File backupDir) {
    RollbackData(int rollbackId, File backupDir, int stagedSessionId) {
        this.rollbackId = rollbackId;
        this.backupDir = backupDir;
        this.stagedSessionId = stagedSessionId;
    }
}
+163 −57
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Implementation of service that manages APK level rollbacks.
@@ -156,6 +157,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            }
        }, filter, null, getHandler());

        // NOTE: A new intent filter is being created here because this broadcast
        // doesn't use a data scheme ("package") like above.
        IntentFilter sessionUpdatedFilter = new IntentFilter();
        sessionUpdatedFilter.addAction(PackageInstaller.ACTION_SESSION_UPDATED);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                onStagedSessionUpdated(intent);
            }
        }, sessionUpdatedFilter, null, getHandler());

        IntentFilter enableRollbackFilter = new IntentFilter();
        enableRollbackFilter.addAction(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
        try {
@@ -718,7 +730,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            return false;
        }

        return enableRollbackForSession(session, installedUsers);
        return enableRollbackForSession(session, installedUsers, true);
    }

    /**
@@ -727,7 +739,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
     * the child sessions, not the parent session.
     */
    private boolean enableRollbackForSession(PackageInstaller.SessionInfo session,
            int[] installedUsers) {
            int[] installedUsers, boolean snapshotUserData) {
        // TODO: Don't attempt to enable rollback for split installs.
        final int installFlags = session.installFlags;
        if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
@@ -749,15 +761,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        }

        String packageName = newPackage.packageName;
        Log.i(TAG, "Enabling rollback for install of " + packageName);
        Log.i(TAG, "Enabling rollback for install of " + packageName
                + ", session:" + session.sessionId);

        VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
        final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);

        // Get information about the currently installed package.
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pkgInfo = null;
        try {
            pkgInfo = pm.getPackageInfo(packageName, 0);
            pkgInfo = pm.getPackageInfo(packageName, isApex ? PackageManager.MATCH_APEX : 0);
        } catch (PackageManager.NameNotFoundException e) {
            // TODO: Support rolling back fresh package installs rather than
            // fail here. Test this case.
@@ -768,17 +782,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        VersionedPackage installedVersion = new VersionedPackage(packageName,
                pkgInfo.getLongVersionCode());

        final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
        final IntArray pendingBackups;
        if (isApex) {
            pendingBackups = IntArray.wrap(new int[0]);
        } else {
        IntArray pendingBackups = IntArray.wrap(new int[0]);
        if (snapshotUserData && !isApex) {
            pendingBackups = mUserdataHelper.snapshotAppData(packageName, installedUsers);
        }

        // TODO: Record if this is an apex or not.
        PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion,
                pendingBackups, new ArrayList<>());
                pendingBackups, new ArrayList<>(), isApex);
        RollbackData data;
        try {
            int childSessionId = session.getSessionId();
@@ -786,6 +796,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            if (parentSessionId == PackageInstaller.SessionInfo.INVALID_ID) {
                parentSessionId = childSessionId;
            }

            synchronized (mLock) {
                // TODO: no need to add to mChildSessions if childSessionId is
                // the same as parentSessionId.
@@ -793,7 +804,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
                data = mPendingRollbacks.get(parentSessionId);
                if (data == null) {
                    int rollbackId = allocateRollbackIdLocked();
                    if (session.isStaged()) {
                        data = mRollbackStore.createPendingStagedRollback(rollbackId,
                                parentSessionId);
                    } else {
                        data = mRollbackStore.createAvailableRollback(rollbackId);
                    }
                    mPendingRollbacks.put(parentSessionId, data);
                }
                data.packages.add(info);
@@ -844,6 +860,56 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
        });
    }

    @Override
    public boolean notifyStagedSession(int sessionId) {
        final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();

        // NOTE: We post this runnable on the RollbackManager's binder thread because we'd prefer
        // to preserve the invariant that all operations that modify state happen there.
        getHandler().post(() -> {
            PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();

            final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
            if (session == null) {
                Log.e(TAG, "No matching install session for: " + sessionId);
                result.offer(false);
                return;
            }

            if (!session.isMultiPackage()) {
                if (!enableRollbackForSession(session, null, false)) {
                    Log.e(TAG, "Unable to enable rollback for session: " + sessionId);
                    result.offer(false);
                    return;
                }
            } else {
                for (int childSessionId : session.getChildSessionIds()) {
                    final PackageInstaller.SessionInfo childSession =
                            installer.getSessionInfo(childSessionId);
                    if (childSession == null) {
                        Log.e(TAG, "No matching child install session for: " + childSessionId);
                        result.offer(false);
                        return;
                    }
                    if (!enableRollbackForSession(childSession, null, false)) {
                        Log.e(TAG, "Unable to enable rollback for session: " + sessionId);
                        result.offer(false);
                        return;
                    }
                }
            }

            result.offer(true);
        });

        try {
            return result.take();
        } catch (InterruptedException ie) {
            Log.e(TAG, "Interrupted while waiting for notifyStagedSession response");
            return false;
        }
    }

    /**
     * Gets the version of the package currently installed.
     * Returns null if the package is not currently installed.
@@ -881,12 +947,28 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {

        @Override
        public void onFinished(int sessionId, boolean success) {
            // If sessionId refers to a staged session, we can't deal with it here since the
            // session might take an unbounded amount of time to become "ready" after the package
            // installer session is committed. In those cases, we respond to it in response to
            // a session ready broadcast.
            PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo si = packageInstaller.getSessionInfo(sessionId);
            if (si != null && si.isStaged()) {
                return;
            }

            completeEnableRollback(sessionId, success);
        }
    }

    private void completeEnableRollback(int sessionId, boolean success) {
        RollbackData data = null;
        synchronized (mLock) {
            Integer parentSessionId = mChildSessions.remove(sessionId);
            if (parentSessionId != null) {
                sessionId = parentSessionId;
            }

            data = mPendingRollbacks.remove(sessionId);
        }

@@ -933,6 +1015,30 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
            }
        }
    }

    private void onStagedSessionUpdated(Intent intent) {
        PackageInstaller.SessionInfo pi = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
        if (pi == null) {
            Log.e(TAG, "Missing intent extra: " + PackageInstaller.EXTRA_SESSION);
            return;
        }

        if (pi.isStaged()) {
            if (!pi.isSessionFailed()) {
                // TODO: The session really isn't "enabled" at this point, since more work might
                // be required post reboot.
                // TODO: We need to make this case consistent with the call from onFinished.
                //  Ideally, we'd call completeEnableRollback excatly once per multi-package session
                //  with the parentSessionId only.
                completeEnableRollback(pi.sessionId, pi.isSessionReady());
            } else {
                // TODO: Clean up the saved rollback when the session fails. This may need to be
                // unified with the case where things fail post reboot.
            }
        } else {
            Log.e(TAG, "Received onStagedSessionUpdated for: " + pi.sessionId
                    + ", which isn't staged");
        }
    }

    /*
Loading