Skip to content

feat(updater): silent background pre-download and instant install for Compose updater - #3220

Open
codeiva11 wants to merge 1 commit into
recloudstream:masterfrom
codeiva11:feat/silent-compose-updater
Open

codeiva11 wants to merge 1 commit into
recloudstream:masterfrom
codeiva11:feat/silent-compose-updater

Conversation

@codeiva11

Copy link
Copy Markdown

Summary

Builds directly on top of the newly merged Compose updater (#3204) by introducing silent background pre-downloading and immediate installation from cached APKs with real-time in-app progress.

What this adds to the Compose updater

  1. Silent Background Pre-download:

    • In \ApkUpdater.kt, added \downloadSilently\ to fetch updates quietly in the background without UI interruption during automated checks (\AutoSearchForUpdate).
    • The user is only prompted once the APK is fully downloaded and verified on disk.
  2. Instant Installation from Cache:

    • In \GithubViewModel.kt, clicking Update checks if the APK is already pre-downloaded in internal cache.
    • If present, \ApkUpdater.installFromFile\ immediately writes to \PackageInstaller.Session\ without download wait times.
    • Falls back gracefully to standard live download if cache is missing.
  3. In-App Installation Progress in Compose:

    • Added \GithubUpdateDialogState.InstallProgress\ to \MainActivityScreen.kt.
    • Displays real-time 0%–100% horizontal progress while writing to the session.
    • Re-enabled \AutoSearchForUpdate\ on \MainActivityScreen\ to allow automated background update discovery.
  4. Cache Clean-up on Skip:

    • When the user chooses Skip Update, the pre-downloaded APK is immediately deleted from the cache to preserve storage.

Verification

  • Tested compilation: ./gradlew compilePrereleaseDebugKotlin\ (Build Successful).
  • Verified Kotlin ABI compatibility: ./gradlew library:checkKotlinAbi\ (Passed).
  • Verified Unit Tests: ./gradlew testPrereleaseDebugUnitTest\ (Passed).

Comment on lines +96 to +100
if (tempFile.renameTo(targetFile)) {
targetFile
} else {
tempFile
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this rename fail? In case it's because targetFile could still exist, we should delete targetFile first.

Comment on lines +120 to +126
val installParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
installParams.setSize(length)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val installParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
installParams.setSize(length)
val installParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
installParams.setSize(length)

Please properly group code.


// If automated background search, download the update APK silently in advance
if (!fromUser) {
val activity = com.lagradost.cloudstream3.CommonActivity.activity

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val activity = com.lagradost.cloudstream3.CommonActivity.activity
val activity = CommonActivity.activity

)

private fun deleteCachedApk(tagName: String) {
val activity = com.lagradost.cloudstream3.CommonActivity.activity

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val activity = com.lagradost.cloudstream3.CommonActivity.activity
val activity = CommonActivity.activity

Comment on lines +136 to +143

val receiverIntent = Intent(activity, PackageInstallerStatusReceiver::class.java)
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val receiverPendingIntent = PendingIntent.getBroadcast(activity, 0, receiverIntent, flags)

@Bnyro Bnyro Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's possible to use PendingIntentCompat instead to simplify the code?

Comment on lines +117 to +151
var sessionId: Int? = null
val packageInstaller = activity.packageManager.packageInstaller
try {
val installParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
installParams.setSize(length)

sessionId = packageInstaller.createSession(installParams)
val session = packageInstaller.openSession(sessionId)

session.openWrite(activity.packageName, 0, length)
.use { writeStream ->
transfer(writeStream, inputStream, length, installProgress, null)
session.fsync(writeStream)
}

val receiverIntent = Intent(activity, PackageInstallerStatusReceiver::class.java)
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val receiverPendingIntent = PendingIntent.getBroadcast(activity, 0, receiverIntent, flags)
session.commit(receiverPendingIntent.intentSender)
session.close()
} catch (t: Throwable) {
sessionId?.let { id ->
packageInstaller.abandonSession(id)
}
throw t
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all just copy and paste from packageInstallerDownloader, you should move it into a new, shared method instead of copy and pasting.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants