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.
This commit is contained in:
Rahul Patel
2026-05-30 00:29:49 +05:30
parent ab9b66eb94
commit 7e8746dbcb
3 changed files with 19 additions and 0 deletions

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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) {}
}