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:
@@ -162,6 +162,8 @@ class DownloadHelper @Inject constructor(
|
|||||||
suspend fun cancelDownload(packageName: String) {
|
suspend fun cancelDownload(packageName: String) {
|
||||||
Log.i(TAG, "Cancelling download for $packageName")
|
Log.i(TAG, "Cancelling download for $packageName")
|
||||||
WorkManager.getInstance(context).cancelAllWorkByTag("$PACKAGE_NAME:$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)
|
downloadDao.updateStatus(packageName, DownloadStatus.CANCELLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
private fun stageInstall(
|
||||||
packageName: String,
|
packageName: String,
|
||||||
versionCode: Long,
|
versionCode: Long,
|
||||||
|
|||||||
@@ -26,4 +26,11 @@ interface IInstaller {
|
|||||||
fun clearQueue()
|
fun clearQueue()
|
||||||
fun isAlreadyQueued(packageName: String): Boolean
|
fun isAlreadyQueued(packageName: String): Boolean
|
||||||
fun removeFromInstallQueue(packageName: String)
|
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) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user