diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 1e6cea39b44dd..5d6867e143938 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1211,6 +1211,9 @@ public class PackageInstaller { * Adds a session ID to the set of sessions that will be committed atomically * when this session is committed. * + *
If the parent is staged or has rollback enabled, all children must have + * the same properties. + * * @param sessionId the session ID to add to this multi-package session. */ public void addChildSessionId(int sessionId) { @@ -1480,6 +1483,9 @@ public class PackageInstaller { /** * Request that rollbacks be enabled or disabled for the given upgrade. * + *
If the parent session is staged or has rollback enabled, all children sessions + * must have the same properties. + * * @param enable set to {@code true} to enable, {@code false} to disable * @hide */ @@ -1607,6 +1613,9 @@ public class PackageInstaller { * multi-package. In that case, if any of the children sessions fail to install at reboot, * all the other children sessions are aborted as well. * + *
If the parent session is staged or has rollback enabled, all children sessions
+ * must have the same properties.
+ *
* {@hide}
*/
@SystemApi @TestApi
@@ -1626,6 +1635,11 @@ public class PackageInstaller {
installFlags |= PackageManager.INSTALL_APEX;
}
+ /** @hide */
+ public boolean getEnableRollback() {
+ return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b190b34aa03fd..254238f68afe3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1397,6 +1397,14 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS = -119;
+ /**
+ * Installation failed return code: one of the child sessions does not match the parent session
+ * in respect to staged or rollback enabled parameters.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY = -120;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ad17549d7448e..7c04cddcaad2c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -396,6 +396,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
} finally {
IoUtils.closeQuietly(fis);
}
+ // After all of the sessions were loaded, they are ready to be sealed and validated
+ for (int i = 0; i < mSessions.size(); ++i) {
+ PackageInstallerSession session = mSessions.valueAt(i);
+ session.sealAndValidateIfNecessary();
+ }
}
@GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f1d4524cccac3..5d539a4b1dda7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -231,6 +231,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private boolean mSealed = false;
@GuardedBy("mLock")
+ private boolean mShouldBeSealed = false;
+ @GuardedBy("mLock")
private boolean mCommitted = false;
@GuardedBy("mLock")
private boolean mRelinquished = false;
@@ -430,6 +432,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
this.updatedMillis = createdMillis;
this.stageDir = stageDir;
this.stageCid = stageCid;
+ this.mShouldBeSealed = sealed;
if (childSessionIds != null) {
for (int childSessionId : childSessionIds) {
mChildSessionIds.put(childSessionId, 0);
@@ -450,16 +453,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagedSessionErrorCode = stagedSessionErrorCode;
mStagedSessionErrorMessage =
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
- if (sealed) {
- synchronized (mLock) {
- try {
- sealAndValidateLocked();
- } catch (PackageManagerException | IOException e) {
- destroyInternal();
- throw new IllegalArgumentException(e);
- }
- }
- }
}
public SessionInfo generateInfo() {
@@ -932,6 +925,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@NonNull IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
+ List This method is handy to prevent potential deadlocks (b/123391593)
+ */
+ private @Nullable List The session will be sealed after calling this method even if it failed.
*
+ * @param childSessions the child sessions of a multipackage that will be checked for
+ * consistency. Can be null if session is not multipackage.
* @throws PackageManagerException if the session was sealed but something went wrong. If the
* session was sealed this is the only possible exception.
*/
@GuardedBy("mLock")
- private void sealAndValidateLocked() throws PackageManagerException, IOException {
+ private void sealAndValidateLocked(List This is meant to be called after all of the sessions are loaded and added to
+ * PackageInstallerService
+ */
+ void sealAndValidateIfNecessary() {
+ synchronized (mLock) {
+ if (!mShouldBeSealed) {
+ return;
+ }
+ }
+ List