summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/content/rollback/IRollbackManager.aidl3
-rw-r--r--services/core/java/com/android/server/pm/ApexManager.java15
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java84
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java4
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java17
7 files changed, 105 insertions, 49 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6e890ba0d827..94af5416aa8d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -946,7 +946,8 @@ public abstract class PackageManager {
INSTALL_REASON_POLICY,
INSTALL_REASON_DEVICE_RESTORE,
INSTALL_REASON_DEVICE_SETUP,
- INSTALL_REASON_USER
+ INSTALL_REASON_USER,
+ INSTALL_REASON_ROLLBACK
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallReason {}
@@ -977,6 +978,13 @@ public abstract class PackageManager {
public static final int INSTALL_REASON_USER = 4;
/**
+ * Code indicating that the package installation was a rollback initiated by RollbackManager.
+ *
+ * @hide
+ */
+ public static final int INSTALL_REASON_ROLLBACK = 5;
+
+ /**
* @hide
*/
public static final int INSTALL_UNKNOWN = 0;
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 8c2a65faf8ed..cda0e9865054 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -44,9 +44,10 @@ interface IRollbackManager {
// 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.
+ // Returns the rollback id if rollback was enabled successfully, or -1 if not.
//
// NOTE: This call is synchronous.
- boolean notifyStagedSession(int sessionId);
+ int notifyStagedSession(int sessionId);
// Used by the staging manager to notify the RollbackManager of the apk
// session for a staged session.
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2b4b409f329a..5ae8c5881cb1 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -17,11 +17,11 @@
package com.android.server.pm;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -176,13 +176,9 @@ abstract class ApexManager {
* enough for it to be activated at the next boot, the caller needs to call
* {@link #markStagedSessionReady(int)}.
*
- * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
- * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
- * an array of identifiers of all the child sessions. Otherwise it should
- * be an empty array.
* @throws PackageManagerException if call to apexd fails
*/
- abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
+ abstract ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException;
/**
@@ -450,11 +446,10 @@ abstract class ApexManager {
}
@Override
- ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
- throws PackageManagerException {
+ ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
- mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
+ mApexService.submitStagedSession(params, apexInfoList);
return apexInfoList;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
@@ -686,7 +681,7 @@ abstract class ApexManager {
}
@Override
- ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
+ ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"Device doesn't support updating APEX");
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 7ea4e984b426..688c34fed44f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -36,6 +37,8 @@ import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.rollback.IRollbackManager;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -49,6 +52,7 @@ import android.os.storage.StorageManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
@@ -82,6 +86,9 @@ public class StagingManager {
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
+ @GuardedBy("mStagedSessions")
+ private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
+
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
mPi = pi;
mApexManager = am;
@@ -166,18 +173,32 @@ public class StagingManager {
private List<PackageInfo> submitSessionToApexService(
@NonNull PackageInstallerSession session) throws PackageManagerException {
- final IntArray childSessionsIds = new IntArray();
+ final IntArray childSessionIds = new IntArray();
if (session.isMultiPackage()) {
for (int id : session.getChildSessionIds()) {
if (isApexSession(mStagedSessions.get(id))) {
- childSessionsIds.add(id);
+ childSessionIds.add(id);
+ }
+ }
+ }
+ ApexSessionParams apexSessionParams = new ApexSessionParams();
+ apexSessionParams.sessionId = session.sessionId;
+ apexSessionParams.childSessionIds = childSessionIds.toArray();
+ if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
+ apexSessionParams.isRollback = true;
+ apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
+ } else {
+ synchronized (mStagedSessions) {
+ int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
+ if (rollbackId != -1) {
+ apexSessionParams.hasRollbackEnabled = true;
+ apexSessionParams.rollbackId = rollbackId;
}
}
}
// submitStagedSession will throw a PackageManagerException if apexd verification fails,
// which will be propagated to populate stagedSessionErrorMessage of this session.
- final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
- childSessionsIds.toArray());
+ final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
final List<PackageInfo> result = new ArrayList<>();
for (ApexInfo apexInfo : apexInfoList.apexInfos) {
final PackageInfo packageInfo;
@@ -208,6 +229,19 @@ public class StagingManager {
return result;
}
+ private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
+ RollbackManager rm = mContext.getSystemService(RollbackManager.class);
+
+ List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.getCommittedSessionId() == sessionId) {
+ return rollback.getRollbackId();
+ }
+ }
+ throw new PackageManagerException(
+ "Could not find rollback id for commit session: " + sessionId);
+ }
+
private void checkRequiredVersionCode(final PackageInstallerSession session,
final PackageInfo activePackage) throws PackageManagerException {
if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
@@ -633,6 +667,7 @@ public class StagingManager {
void abortSession(@NonNull PackageInstallerSession session) {
synchronized (mStagedSessions) {
mStagedSessions.remove(session.sessionId);
+ mSessionRollbackIds.delete(session.sessionId);
}
}
@@ -865,6 +900,28 @@ public class StagingManager {
*/
private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
+
+ 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.
+ int rollbackId = rm.notifyStagedSession(session.sessionId);
+ if (rollbackId != -1) {
+ synchronized (mStagedSessions) {
+ mSessionRollbackIds.put(session.sessionId, rollbackId);
+ }
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Failed to notifyStagedSession for session: "
+ + session.sessionId, re);
+ }
+ }
+
notifyPreRebootVerification_Start_Complete(session.sessionId);
}
@@ -929,25 +986,6 @@ public class StagingManager {
* </ul></p>
*/
private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
- 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) {
- Slog.e(TAG, "Failed to notifyStagedSession for session: "
- + session.sessionId, re);
- }
- }
-
// Proactively mark session as ready before calling apexd. Although this call order
// looks counter-intuitive, this is the easiest way to ensure that session won't end up
// in the inconsistent state:
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 8b79c3ff66c6..6898e1cd1f83 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -418,6 +418,7 @@ class Rollback {
if (isStaged()) {
parentParams.setStaged();
}
+ parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
int parentSessionId = packageInstaller.createSession(parentParams);
PackageInstaller.Session parentSession = packageInstaller.openSession(
@@ -484,6 +485,7 @@ class Rollback {
synchronized (mLock) {
mState = ROLLBACK_STATE_AVAILABLE;
mRestoreUserDataInProgress = false;
+ info.setCommittedSessionId(-1);
}
sendFailure(context, statusReceiver,
RollbackManager.STATUS_FAILURE_INSTALL,
@@ -500,7 +502,6 @@ class Rollback {
mRestoreUserDataInProgress = false;
}
- info.setCommittedSessionId(parentSessionId);
info.getCausePackages().addAll(causePackages);
RollbackStore.deletePackageCodePaths(this);
RollbackStore.saveRollback(this);
@@ -528,6 +529,7 @@ class Rollback {
);
mState = ROLLBACK_STATE_COMMITTED;
+ info.setCommittedSessionId(parentSessionId);
mRestoreUserDataInProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3f5e2a447d28..9324870904d9 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -897,11 +897,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
@Override
- public boolean notifyStagedSession(int sessionId) {
+ public int notifyStagedSession(int sessionId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("notifyStagedSession may only be called by the system.");
}
- final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();
+ final LinkedBlockingQueue<Integer> 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.
@@ -911,7 +911,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
if (session == null) {
Slog.e(TAG, "No matching install session for: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
@@ -923,7 +923,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
if (!session.isMultiPackage()) {
if (!enableRollbackForPackageSession(newRollback.rollback, session)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
} else {
@@ -932,25 +932,30 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
installer.getSessionInfo(childSessionId);
if (childSession == null) {
Slog.e(TAG, "No matching child install session for: " + childSessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
if (!enableRollbackForPackageSession(newRollback.rollback, childSession)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
}
}
- result.offer(completeEnableRollback(newRollback, true) != null);
+ Rollback rollback = completeEnableRollback(newRollback, true);
+ if (rollback == null) {
+ result.offer(-1);
+ } else {
+ result.offer(rollback.info.getRollbackId());
+ }
});
try {
return result.take();
} catch (InterruptedException ie) {
Slog.e(TAG, "Interrupted while waiting for notifyStagedSession response");
- return false;
+ return -1;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index fb9c68a5b70d..40ada2aedd59 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -30,6 +30,7 @@ import static org.testng.Assert.assertThrows;
import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -183,19 +184,18 @@ public class ApexManagerTest {
public void testSubmitStagedSession_throwPackageManagerException() throws RemoteException {
doAnswer(invocation -> {
throw new Exception();
- }).when(mApexService).submitStagedSession(anyInt(), any(), any());
+ }).when(mApexService).submitStagedSession(any(), any());
assertThrows(PackageManagerException.class,
- () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+ () -> mApexManager.submitStagedSession(testParamsWithChildren()));
}
@Test
public void testSubmitStagedSession_throwRunTimeException() throws RemoteException {
- doThrow(RemoteException.class).when(mApexService).submitStagedSession(anyInt(), any(),
- any());
+ doThrow(RemoteException.class).when(mApexService).submitStagedSession(any(), any());
assertThrows(RuntimeException.class,
- () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+ () -> mApexManager.submitStagedSession(testParamsWithChildren()));
}
@Test
@@ -272,6 +272,13 @@ public class ApexManagerTest {
return stagedSessionInfo;
}
+ private static ApexSessionParams testParamsWithChildren() {
+ ApexSessionParams params = new ApexSessionParams();
+ params.sessionId = TEST_SESSION_ID;
+ params.childSessionIds = TEST_CHILD_SESSION_ID;
+ return params;
+ }
+
/**
* Copies a specified {@code resourceId} to a temp file. Returns a non-null file if the copy
* succeeded