Merge "[automerger skipped] Merge "Check permissions and carrier privilege in notifyActiveDataSubIdChanged" am: 801cd8ff8a am: 27da4d70d4 am: 53b205a5f9 -s ours am skip reason: change_id I1d9c5d1b242953a2af3e56718ef82761941d8d9c with SHA1 9e9ed18772 is in history"
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1e6cea3..5d6867e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1211,6 +1211,9 @@
* Adds a session ID to the set of sessions that will be committed atomically
* when this session is committed.
*
+ * <p>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 @@
/**
* Request that rollbacks be enabled or disabled for the given upgrade.
*
+ * <p>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 @@
* 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.
*
+ * <p>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 @@
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 cf2ae7d..6dd60b1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1397,6 +1397,14 @@
*/
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 afa5ae9..ce3c452 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 @@
} 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 f1d4524..5d539a4 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 @@
@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 @@
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 @@
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 @@
@NonNull IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
+ List<PackageInstallerSession> childSessions = getChildSessions();
+
final boolean wasSealed;
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
@@ -963,7 +958,7 @@
wasSealed = mSealed;
if (!mSealed) {
try {
- sealAndValidateLocked();
+ sealAndValidateLocked(childSessions);
} catch (IOException e) {
throw new IllegalArgumentException(e);
} catch (PackageManagerException e) {
@@ -994,21 +989,91 @@
return true;
}
+ /** Return a list of child sessions or null if the session is not multipackage
+ *
+ * <p> This method is handy to prevent potential deadlocks (b/123391593)
+ */
+ private @Nullable List<PackageInstallerSession> getChildSessions() {
+ List<PackageInstallerSession> childSessions = null;
+ if (isMultiPackage()) {
+ final int[] childSessionIds = getChildSessionIds();
+ childSessions = new ArrayList<>(childSessionIds.length);
+ for (int childSessionId : childSessionIds) {
+ childSessions.add(mSessionProvider.getSession(childSessionId));
+ }
+ }
+ return childSessions;
+ }
+
+ /**
+ * Assert multipackage install has consistent sessions.
+ *
+ * @throws PackageManagerException if child sessions don't match parent session
+ * in respect to staged and enable rollback parameters.
+ */
+ @GuardedBy("mLock")
+ private void assertMultiPackageConsistencyLocked(
+ @NonNull List<PackageInstallerSession> childSessions) throws PackageManagerException {
+ for (PackageInstallerSession childSession : childSessions) {
+ // It might be that the parent session is loaded before all of it's child sessions are,
+ // e.g. when reading sessions from XML. Those sessions will be null here, and their
+ // conformance with the multipackage params will be checked when they're loaded.
+ if (childSession == null) {
+ continue;
+ }
+ assertConsistencyWithLocked(childSession);
+ }
+ }
+
+ /**
+ * Assert consistency with the given session.
+ *
+ * @throws PackageManagerException if other sessions doesn't match this session
+ * in respect to staged and enable rollback parameters.
+ */
+ @GuardedBy("mLock")
+ private void assertConsistencyWithLocked(PackageInstallerSession other)
+ throws PackageManagerException {
+ // Session groups must be consistent wrt to isStaged parameter. Non-staging session
+ // cannot be grouped with staging sessions.
+ if (this.params.isStaged != other.params.isStaged) {
+ throw new PackageManagerException(
+ PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
+ "Multipackage Inconsistency: session " + other.sessionId
+ + " and session " + sessionId
+ + " have inconsistent staged settings");
+ }
+ if (this.params.getEnableRollback() != other.params.getEnableRollback()) {
+ throw new PackageManagerException(
+ PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY,
+ "Multipackage Inconsistency: session " + other.sessionId
+ + " and session " + sessionId
+ + " have inconsistent rollback settings");
+ }
+ }
+
/**
* Seal the session to prevent further modification and validate the contents of it.
*
* <p>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<PackageInstallerSession> childSessions)
+ throws PackageManagerException, IOException {
assertNoWriteFileTransfersOpenLocked();
assertPreparedAndNotDestroyedLocked("sealing of session");
mSealed = true;
+ if (childSessions != null) {
+ assertMultiPackageConsistencyLocked(childSessions);
+ }
+
if (params.isStaged) {
final PackageInstallerSession activeSession = mStagingManager.getActiveSession();
final boolean anotherSessionAlreadyInProgress =
@@ -1048,6 +1113,38 @@
}
}
+ /**
+ * If session should be sealed, then it's sealed to prevent further modification
+ * and then it's validated.
+ *
+ * If the session was sealed but something went wrong then it's destroyed.
+ *
+ * <p> 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<PackageInstallerSession> childSessions = getChildSessions();
+ synchronized (mLock) {
+ try {
+ sealAndValidateLocked(childSessions);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "Package not valid", e);
+ // Session is sealed but could not be verified, we need to destroy it.
+ destroyInternal();
+ // Dispatch message to remove session from PackageInstallerService
+ dispatchSessionFinished(
+ e.error, ExceptionUtils.getCompleteMessage(e), null);
+ }
+ }
+ }
+
/** Update the timestamp of when the staged session last changed state */
public void markUpdated() {
synchronized (mLock) {
@@ -1076,12 +1173,14 @@
throw new SecurityException("Can only transfer sessions that use public options");
}
+ List<PackageInstallerSession> childSessions = getChildSessions();
+
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("transfer");
try {
- sealAndValidateLocked();
+ sealAndValidateLocked(childSessions);
} catch (IOException e) {
throw new IllegalStateException(e);
} catch (PackageManagerException e) {
@@ -1132,14 +1231,7 @@
// outside of the lock, because reading the child
// sessions with the lock held could lead to deadlock
// (b/123391593).
- List<PackageInstallerSession> childSessions = null;
- if (isMultiPackage()) {
- final int[] childSessionIds = getChildSessionIds();
- childSessions = new ArrayList<>(childSessionIds.length);
- for (int childSessionId : childSessionIds) {
- childSessions.add(mSessionProvider.getSession(childSessionId));
- }
- }
+ List<PackageInstallerSession> childSessions = getChildSessions();
try {
synchronized (mLock) {
@@ -1965,15 +2057,6 @@
+ " does not exist"),
false, true).rethrowAsRuntimeException();
}
- // Session groups must be consistent wrt to isStaged parameter. Non-staging session
- // cannot be grouped with staging sessions.
- if (this.params.isStaged ^ childSession.params.isStaged) {
- throw new RemoteException("Unable to add child.",
- new PackageManagerException("Child session " + childSessionId
- + " and parent session " + this.sessionId + " do not have consistent"
- + " staging session settings."),
- false, true);
- }
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addChildSessionId");