diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3d63dcf7868ff..958c769ba7d19 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2112,6 +2112,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
Slog.e(TAG, "Failed to verify session " + sessionId + " [" + msgWithErrorCode + "]");
// Session is sealed and committed but could not be verified, we need to destroy it.
destroyInternal();
+ if (isMultiPackage()) {
+ for (PackageInstallerSession childSession : getChildSessions()) {
+ childSession.destroyInternal();
+ }
+ }
if (isStaged()) {
mStagedSession.setSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
index dc8c8113fac2e..f584599e81006 100644
--- a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
@@ -20,6 +20,13 @@
+
+
+
+
+
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/PackageInstallerSessionTest.kt
new file mode 100644
index 0000000000000..86571abcbb380
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/PackageInstallerSessionTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import kotlin.jvm.JvmField
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class PackageInstallerSessionTest : BaseHostJUnit4Test() {
+ companion object {
+ private const val DEVICE_SIDE = "PackageManagerServiceDeviceSideTests.apk"
+ }
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun verify_parentSessionFail_childSessionFiles_shouldBeDestroyed() {
+ runDeviceTest("com.android.server.pm.PackageInstallerSessionTest",
+ "verify_parentSessionFail_childSessionFiles_shouldBeDestroyed")
+ }
+
+ /**
+ * Run a device side test from com.android.server.pm.test.deviceside.DeviceSide
+ *
+ * @param method the method to run
+ */
+ fun runDeviceTest(testClassName: String, method: String) {
+ val deviceSideFile = HostUtils.copyResourceToHostFile(DEVICE_SIDE, tempFolder.newFile())
+ Truth.assertThat(device.installPackage(deviceSideFile, true)).isNull()
+ runDeviceTests(device, "com.android.server.pm.test.deviceside",
+ testClassName, method)
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
index 7e4f0e72b62d0..53adc2fb00e69 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/Android.bp
@@ -32,6 +32,8 @@ android_test_helper_app {
],
static_libs: [
"androidx.annotation_annotation",
+ "commands-helper",
+ "cts-install-lib",
"junit",
"junit-params",
"androidx.test.ext.junit",
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml
index 286ad56435fdd..dd6dea78d08b0 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/DeviceSide/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="com.android.server.pm.test.deviceside">
+
+
return parentSession.childSessionIds
+ }
+ }
+
+ private fun getSessionStageDir(sessionId: Int): String? {
+ val commandsHelper: CommandsHelper = CommandsHelper.getInstance(mInstrumentation)
+ val dumpsysForPackage: MutableList? = commandsHelper
+ .executeShellCommandAndSplitOutput("dumpsys package", "\\n")
+ val pattern = Regex(" stageDir=(\\S+${sessionId}\\S+) ")
+ val matchStageDirs: ArrayList = ArrayList()
+ dumpsysForPackage?.forEach { line ->
+ val matchResult: MatchResult? = pattern.find(line)
+ if (matchResult != null) {
+ val (stageDir: String) = matchResult.destructured
+ matchStageDirs.add(stageDir)
+ }
+ }
+
+ if (matchStageDirs.size > 0) {
+ return matchStageDirs[0]
+ }
+ return null
+ }
+
+ private fun getSessionStageDirInfo(sessionId: Int): String? {
+ SystemUtil.runWithShellPermissionIdentity {
+ val sessionStageDir =
+ getSessionStageDir(sessionId) ?: return@runWithShellPermissionIdentity
+ val command = "su root ls $sessionStageDir"
+ val lines: List = CommandsHelper.getInstance(mInstrumentation)
+ .executeShellCommandAndSplitOutput(command, "\\n")
+ val sessionIdStr = sessionId.toString()
+ for (line in lines) {
+ if (line.contains(sessionIdStr)) {
+ mChildSessionStageDirInfo = line
+ }
+ }
+ }
+ return mChildSessionStageDirInfo
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun verify_parentSessionFail_childSessionFiles_shouldBeDestroyed() {
+ val context: Context = mInstrumentation.targetContext
+ Install.single(TestApp.A3).commit()
+ val parentSessionId: Int = Install.multi(TestApp.A1).createSession()
+ val childSessionIds: IntArray = getChildSessionIds(parentSessionId)
+ val firstChildSessionId = childSessionIds[0]
+
+ val sender = LocalIntentSender()
+ try {
+ InstallUtils.openPackageInstallerSession(parentSessionId).use { session ->
+ session.commit(sender.intentSender)
+ val result = sender.result
+ InstallUtils.assertStatusFailure(result)
+ }
+ } finally {
+ context.unregisterReceiver(sender)
+ }
+
+ Truth.assertThat(getSessionStageDirInfo(firstChildSessionId)).isNull()
+ }
+}