@@ -33,15 +33,19 @@ import android.graphics.BitmapFactory
3333import android.icu.util.ULocale
3434import android.net.Uri
3535import android.os.Build
36+ import android.os.CancellationSignal
3637import android.os.Handler
3738import android.os.OperationCanceledException
3839import android.util.Log
3940import androidx.annotation.ChecksSdkIntAtLeast
4041import androidx.annotation.RequiresApi
4142import androidx.annotation.RestrictTo
4243import androidx.concurrent.futures.CallbackToFutureAdapter
44+ import ru.solrudev.ackpine.helpers.closeAllWithException
4345import ru.solrudev.ackpine.helpers.closeWithException
4446import ru.solrudev.ackpine.helpers.concurrent.handleResult
47+ import ru.solrudev.ackpine.helpers.getOrElse
48+ import ru.solrudev.ackpine.helpers.mapCatchingFirst
4549import ru.solrudev.ackpine.helpers.use
4650import ru.solrudev.ackpine.impl.database.dao.InstallConstraintsDao
4751import ru.solrudev.ackpine.impl.database.dao.InstallPreapprovalDao
@@ -75,6 +79,7 @@ import ru.solrudev.ackpine.session.Session.State.Failed
7579import ru.solrudev.ackpine.session.parameters.Confirmation
7680import ru.solrudev.ackpine.session.parameters.NotificationData
7781import java.util.UUID
82+ import java.util.concurrent.CancellationException
7883import java.util.concurrent.Executor
7984import java.util.concurrent.atomic.AtomicInteger
8085import kotlin.random.Random
@@ -235,9 +240,9 @@ internal class SessionBasedInstallSession internal constructor(
235240 }
236241
237242 override fun doCleanup () {
238- executor.execute(::abandonSession) // may be long if storage is under load
239- clearPackageInstallerSessionCallback()
240243 isPreapprovalActive = false
244+ clearPackageInstallerSessionCallback()
245+ executor.execute(::abandonSession) // may be long if storage is under load
241246 }
242247
243248 @ChecksSdkIntAtLeast(Build .VERSION_CODES .UPSIDE_DOWN_CAKE )
@@ -403,7 +408,7 @@ internal class SessionBasedInstallSession internal constructor(
403408
404409 private fun writeApksToSession (sessionId : Int ) {
405410 val session = packageInstaller.openSession(sessionId)
406- session.writeApks ().handleResult(
411+ session.writeApksAsync ().handleResult(
407412 block = {
408413 try {
409414 session.close()
@@ -414,47 +419,75 @@ internal class SessionBasedInstallSession internal constructor(
414419 },
415420 onException = { exception ->
416421 session.closeWithException(exception)
417- if (exception is OperationCanceledException ) {
422+ if (exception is CancellationException ) {
418423 cancel()
419424 } else {
420425 completeExceptionally(exception)
421426 }
422427 })
423428 }
424429
425- private fun PackageInstallerService.Session.writeApks () = CallbackToFutureAdapter .getFuture { completer ->
430+ private fun PackageInstallerService.Session.writeApksAsync () = CallbackToFutureAdapter .getFuture { completer ->
431+ writeApks(completer)
432+ }
433+
434+ private fun PackageInstallerService.Session.writeApks (
435+ completer : CallbackToFutureAdapter .Completer <Unit >
436+ ): String {
437+ val tag = " SessionBasedInstallSession.writeApks"
438+ val assetFileDescriptors = apks
439+ .mapCatchingFirst { uri ->
440+ context.openAssetFileDescriptor(uri, cancellationSignal)
441+ ? : throw NullPointerException (" AssetFileDescriptor was null: $uri " )
442+ }
443+ .getOrElse { failure ->
444+ closeAllWithException(failure.partialResult, failure.exception)
445+ if (failure.exception is OperationCanceledException ) {
446+ completer.setCancelled()
447+ } else {
448+ completer.setException(failure.exception)
449+ }
450+ return tag
451+ }
426452 val countdown = AtomicInteger (apks.size)
427453 val currentProgress = AtomicInteger (0 )
428454 val progressMax = apks.size * PROGRESS_MAX
429- apks.forEachIndexed { index, uri ->
430- val afd = context.openAssetFileDescriptor(uri, cancellationSignal)
431- ? : error(" AssetFileDescriptor was null: $uri " )
455+ val sharedCancelSignal = CancellationSignal ()
456+ cancellationSignal.setOnCancelListener {
457+ sharedCancelSignal.cancel()
458+ completer.setCancelled()
459+ }
460+ assetFileDescriptors.forEachIndexed { index, afd ->
432461 try {
433462 executor.execute {
434463 try {
435464 afd.use {
436- writeApk(afd, index, currentProgress, progressMax)
465+ writeApk(afd, index, currentProgress, progressMax, sharedCancelSignal )
437466 if (countdown.decrementAndGet() == 0 ) {
438467 completer.set(Unit )
439468 }
440469 }
470+ } catch (_: OperationCanceledException ) { // no-op
441471 } catch (throwable: Throwable ) {
472+ sharedCancelSignal.cancel()
442473 completer.setException(throwable)
443474 }
444475 }
445476 } catch (exception: Exception ) {
446477 afd.closeWithException(exception)
478+ sharedCancelSignal.cancel()
447479 completer.setException(exception)
448480 }
449481 }
450- " SessionBasedInstallSession.writeApks "
482+ return tag
451483 }
452484
453485 private fun PackageInstallerService.Session.writeApk (
454486 afd : AssetFileDescriptor ,
455487 index : Int ,
456488 currentProgress : AtomicInteger ,
457- progressMax : Int
489+ progressMax : Int ,
490+ cancellationSignal : CancellationSignal
458491 ) = afd.createInputStream().use { apkStream ->
459492 requireNotNull(apkStream) { " APK $index InputStream was null." }
460493 val length = afd.declaredLength
0 commit comments