diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivityScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivityScreen.kt index 28553b3200e..75e6f70250f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivityScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivityScreen.kt @@ -61,9 +61,9 @@ object MainActivityScreen : Screen { buildSha = activity.currentCommitHash(), settings = settings, updater = ApkUpdater - )/*.apply { - onAction(AutoSearchForUpdate) // Remove this for now - }*/ + ).apply { + onAction(AutoSearchForUpdate) + } } } @@ -171,6 +171,42 @@ object MainActivityScreen : Screen { } } + is GithubUpdateDialogState.InstallProgress -> { + cancelable = false + title = stringResource(R.string.install_update) + body = { + val progress = if (state.total != null && state.total > 0L) { + (state.progress.toFloat() / state.total.toFloat()).coerceIn(0.0f, 1.0f) + } else null + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + if (progress != null) { + Text( + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + text = "${(progress * 100.0f).toInt()}%" + ) + Spacer(modifier = Modifier.height(MaterialTheme.padding.medium)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onBackground, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + progress = { progress } + ) + } else { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onBackground, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) + } + } + } + confirmButton = {} + dismissButton = {} + } + is GithubUpdateDialogState.DownloadProgress -> { cancelable = false title = stringResource(R.string.update_notification_downloading) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt index 08e279b49a0..7d50f7d73f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt @@ -6,7 +6,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller -import android.os.Build import androidx.core.content.FileProvider import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity @@ -65,9 +64,69 @@ object ApkUpdater : AppUpdater { } } - fun clearOldFiles(activity: Activity) { + fun getCachedUpdateFile(context: Context, versionTag: String): File { + return File(context.cacheDir, "${APP_UPDATE_NAME}_$versionTag.$APP_UPDATE_SUFFIX") + } + + suspend fun downloadSilently( + context: Context, + url: String, + versionTag: String, + digest: DigestPair? + ): File? = withContext(Dispatchers.IO) { + try { + val targetFile = getCachedUpdateFile(context, versionTag) + if (targetFile.exists() && targetFile.length() > 0) { + return@withContext targetFile + } + + clearOldFiles(context) + val request = app.get(url) + val length = request.size + val body = request.body + val tempFile = File.createTempFile(APP_UPDATE_NAME, ".$APP_UPDATE_SUFFIX", context.cacheDir) + body.use { body -> + val readStream = body.byteStream() + tempFile.outputStream().use { writeStream -> + transfer(writeStream, readStream, length ?: body.contentLength(), { _, _ -> }, digest) + } + } + + if (targetFile.exists()) { + targetFile.delete() + } + if (tempFile.renameTo(targetFile)) { + targetFile + } else { + tempFile + } + } catch (_: Throwable) { + null + } + } + + @Throws + suspend fun installFromFile( + activity: Activity, + file: File, + settings: AppSettings, + installProgress: (Long, Long?) -> Unit + ) = withContext(Dispatchers.IO) { + when (settings.updates.apkInstaller.get()) { + 0 -> installWithPackageInstaller( + activity = activity, + readStream = file.inputStream(), + length = file.length(), + digest = null, + downloadProgress = installProgress + ) + else -> openApk(activity, file) + } + } + + fun clearOldFiles(context: Context) { // Delete old files - activity.cacheDir.listFiles()?.filter { + context.cacheDir.listFiles()?.filter { it.name.startsWith(APP_UPDATE_NAME) && it.extension == APP_UPDATE_SUFFIX }?.forEach { deleteFileOnExit(it) @@ -83,19 +142,30 @@ object ApkUpdater : AppUpdater { length: Long?, digest: DigestPair?, downloadProgress: (Long, Long?) -> Unit, + ) = withContext(Dispatchers.IO) { + installWithPackageInstaller(activity, readStream, length, digest, downloadProgress) + } + + @Throws + private suspend fun installWithPackageInstaller( + activity: Activity, + readStream: InputStream, + length: Long?, + digest: DigestPair?, + downloadProgress: (Long, Long?) -> Unit, ) = withContext(Dispatchers.IO) { 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) - } - if (length != null) { - installParams.setSize(length) - } + PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) + } + if (length != null) { + setSize(length) + } + } sessionId = packageInstaller.createSession(installParams) val session = packageInstaller.openSession(sessionId) @@ -108,7 +178,7 @@ object ApkUpdater : AppUpdater { } val receiverIntent = Intent(activity, PackageInstallerStatusReceiver::class.java) - val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT } else { PendingIntent.FLAG_UPDATE_CURRENT @@ -120,8 +190,8 @@ object ApkUpdater : AppUpdater { session.commit(receiverPendingIntent.intentSender) session.close() } catch (t: Throwable) { - sessionId?.let { sessionId -> - packageInstaller.abandonSession(sessionId) + sessionId?.let { id -> + packageInstaller.abandonSession(id) } throw t } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt index 99236ab7173..b559366b13b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream4.AppSettings @@ -24,6 +25,7 @@ data class GithubState( sealed class GithubUpdateDialogState { data class Error(val error: Throwable) : GithubUpdateDialogState() data class DownloadProgress(val progress: Long, val total: Long?) : GithubUpdateDialogState() + data class InstallProgress(val progress: Long, val total: Long?) : GithubUpdateDialogState() object Loading : GithubUpdateDialogState() object NoUpdateFound : GithubUpdateDialogState() data class UpdateFound( @@ -153,6 +155,7 @@ class GithubViewModel( is GithubAction.SkipThisUpdate -> { settings.updates.skipUpdate.set(action.file.nodeId) + deleteCachedApk(action.file.tagName) } GithubAction.AutoSearchForUpdate -> { @@ -165,11 +168,12 @@ class GithubViewModel( is GithubAction.SkipUpdate -> { settings.updates.skipUpdate.set(action.file.nodeId) + deleteCachedApk(action.file.tagName) } is GithubAction.Update -> { ioSafe { - installUpdate(action.file.downloadUrl, action.file.digest) + installUpdate(action.file) } } } @@ -193,22 +197,52 @@ class GithubViewModel( } } - private suspend fun installUpdate(url: String, digestPair: String?) = dispatchUpdate { - updater.update( - settings = settings, - url = url, - digest = DigestPair.parse(digestPair), - ) { progress, total -> + private suspend fun installUpdate(file: GithubReleases.GithubFile) = dispatchUpdate { + val activity = CommonActivity.activity + val cachedFile = activity?.let { + ApkUpdater.getCachedUpdateFile(it, file.tagName) + } + + if (activity != null && cachedFile != null && cachedFile.exists() && cachedFile.length() > 0) { updateState { copy( dialog = dialog?.copy( - state = GithubUpdateDialogState.DownloadProgress( - progress = progress, - total = total + state = GithubUpdateDialogState.InstallProgress( + progress = 0, + total = cachedFile.length() ) ) ) } + ApkUpdater.installFromFile(activity, cachedFile, settings) { progress, total -> + updateState { + copy( + dialog = dialog?.copy( + state = GithubUpdateDialogState.InstallProgress( + progress = progress, + total = total + ) + ) + ) + } + } + } else { + updater.update( + settings = settings, + url = file.downloadUrl, + digest = DigestPair.parse(file.digest), + ) { progress, total -> + updateState { + copy( + dialog = dialog?.copy( + state = GithubUpdateDialogState.DownloadProgress( + progress = progress, + total = total + ) + ) + ) + } + } } updateState { copy( @@ -266,6 +300,19 @@ class GithubViewModel( return@dispatchUpdate } + // If automated background search, download the update APK silently in advance + if (!fromUser) { + val activity = CommonActivity.activity + if (activity != null) { + ApkUpdater.downloadSilently( + activity, + release.downloadUrl, + release.tagName, + DigestPair.parse(release.digest) + ) + } + } + updateState { copy( dialog = baseDialog.copy( @@ -296,4 +343,14 @@ class GithubViewModel( userName = remoteUserName, repository = remoteRepository, ) + + private fun deleteCachedApk(tagName: String) { + val activity = CommonActivity.activity + if (activity != null) { + val file = ApkUpdater.getCachedUpdateFile(activity, tagName) + if (file.exists()) { + file.delete() + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66eb3533c4c..f7197f3871f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -593,6 +593,7 @@ Downloading app update… Installing app update… Could not install the new version of the app + Installing update… Legacy PackageInstaller App will be updated upon exit