Loading core/java/android/content/rollback/IRollbackManager.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -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); } core/java/android/content/rollback/PackageRollbackInfo.java +15 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; } Loading @@ -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 = Loading services/core/java/com/android/server/pm/StagingManager.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading services/core/java/com/android/server/rollback/RollbackData.java +8 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; } } services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +163 −57 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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 { Loading Loading @@ -718,7 +730,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } return enableRollbackForSession(session, installedUsers); return enableRollbackForSession(session, installedUsers, true); } /** Loading @@ -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) { Loading @@ -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. Loading @@ -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(); Loading @@ -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. Loading @@ -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); Loading Loading @@ -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. Loading Loading @@ -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); } Loading Loading @@ -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 Loading
core/java/android/content/rollback/IRollbackManager.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -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); }
core/java/android/content/rollback/PackageRollbackInfo.java +15 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; } Loading @@ -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 = Loading
services/core/java/com/android/server/pm/StagingManager.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading
services/core/java/com/android/server/rollback/RollbackData.java +8 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; } }
services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +163 −57 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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 { Loading Loading @@ -718,7 +730,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } return enableRollbackForSession(session, installedUsers); return enableRollbackForSession(session, installedUsers, true); } /** Loading @@ -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) { Loading @@ -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. Loading @@ -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(); Loading @@ -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. Loading @@ -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); Loading Loading @@ -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. Loading Loading @@ -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); } Loading Loading @@ -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