From 7e8746dbcbebebf1a09fd40c428a9c714207bcae Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Sat, 30 May 2026 00:29:49 +0530 Subject: [PATCH] Abandon staged install session when a download is cancelled Cancelling a download cancelled the worker and marked the row CANCELLED but never abandoned a PackageInstaller session that had already been staged for install, leaking it. Add IInstaller.cancelInstall (no-op default, implemented by SessionInstaller) and call it from DownloadHelper.cancelDownload. Cross-process session persistence/reconciliation (committing a session staged before a restart) is left as a follow-up; the startup session cleanup remains, and is now cheap to recover from since CacheWorker keeps the downloaded files. --- .../com/aurora/store/data/helper/DownloadHelper.kt | 2 ++ .../aurora/store/data/installer/SessionInstaller.kt | 10 ++++++++++ .../com/aurora/store/data/installer/base/IInstaller.kt | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt index 663d640e4..44f8ade6e 100644 --- a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt +++ b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt @@ -162,6 +162,8 @@ class DownloadHelper @Inject constructor( suspend fun cancelDownload(packageName: String) { Log.i(TAG, "Cancelling download for $packageName") WorkManager.getInstance(context).cancelAllWorkByTag("$PACKAGE_NAME:$packageName") + // Abandon any session already staged for install so we don't leak it. + runCatching { appInstaller.getPreferredInstaller().cancelInstall(packageName) } downloadDao.updateStatus(packageName, DownloadStatus.CANCELLED) } diff --git a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt index f3726d7f4..413ca79e0 100644 --- a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt @@ -182,6 +182,16 @@ class SessionInstaller @Inject constructor( } } + override fun cancelInstall(packageName: String) { + val sessionSet = enqueuedSessions + .find { set -> set.any { it.packageName == packageName } } ?: return + + Log.i(TAG, "Abandoning staged session(s) for $packageName") + sessionSet.forEach { runCatching { packageInstaller.abandonSession(it.sessionId) } } + enqueuedSessions.remove(sessionSet) + removeFromInstallQueue(packageName) + } + private fun stageInstall( packageName: String, versionCode: Long, diff --git a/app/src/main/java/com/aurora/store/data/installer/base/IInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/base/IInstaller.kt index 2c832110f..10b02f4a6 100644 --- a/app/src/main/java/com/aurora/store/data/installer/base/IInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/base/IInstaller.kt @@ -26,4 +26,11 @@ interface IInstaller { fun clearQueue() fun isAlreadyQueued(packageName: String): Boolean fun removeFromInstallQueue(packageName: String) + + /** + * Abandons any staged-but-uncommitted install session for [packageName] so cancelling + * a download doesn't leak a [android.content.pm.PackageInstaller] session. Default no-op + * for installers that don't stage sessions. + */ + fun cancelInstall(packageName: String) {} }