forked from recloudstream/cloudstream
Updated the in app updater to include notifications and updates without user action on Android 12+
This commit is contained in:
parent
c11f0c101b
commit
751175b3f9
8 changed files with 354 additions and 23 deletions
|
@ -48,7 +48,7 @@ android {
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 55
|
versionCode = 55
|
||||||
versionName = "3.2.6"
|
versionName = "3.3.0"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- Used for player vertical slide -->
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- Used for player vertical slide -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Used for app update -->
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Used for app update -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Used for app notifications on Android 13+ -->
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Used for app notifications on Android 13+ -->
|
||||||
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- not used atm, but code exist that requires it that are not run -->
|
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- Used for Android TV watch next -->
|
||||||
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <!-- Used for updates without prompt -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Used for update service -->
|
||||||
|
|
||||||
<!-- <permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!– Used for getting if vlc is installed –> -->
|
<!-- <permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!– Used for getting if vlc is installed –> -->
|
||||||
<!-- Fixes android tv fuckery -->
|
<!-- Fixes android tv fuckery -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
|
@ -170,6 +173,10 @@
|
||||||
android:name=".ui.ControllerActivity"
|
android:name=".ui.ControllerActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".utils.PackageInstallerService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
|
|
@ -88,7 +88,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
||||||
|
|
|
@ -128,7 +128,7 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
if (activity?.runAutoUpdate(false) == false) {
|
if (activity?.runAutoUpdate(false) == false) {
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
CommonActivity.showToast(
|
showToast(
|
||||||
activity,
|
activity,
|
||||||
R.string.no_update_found,
|
R.string.no_update_found,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
|
|
|
@ -7,16 +7,14 @@ import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.BuildConfig
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.app
|
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import okio.BufferedSink
|
import okio.BufferedSink
|
||||||
|
@ -116,6 +114,7 @@ class InAppUpdater {
|
||||||
)?.groupValues?.get(2)
|
)?.groupValues?.get(2)
|
||||||
}
|
}
|
||||||
}).toList().lastOrNull()
|
}).toList().lastOrNull()
|
||||||
|
|
||||||
val foundAsset = found?.assets?.getOrNull(0)
|
val foundAsset = found?.assets?.getOrNull(0)
|
||||||
val currentVersion = packageName?.let {
|
val currentVersion = packageName?.let {
|
||||||
packageManager.getPackageInfo(
|
packageManager.getPackageInfo(
|
||||||
|
@ -245,6 +244,9 @@ class InAppUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkAutoUpdate if the update check was launched automatically
|
||||||
|
**/
|
||||||
suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean {
|
suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
@ -254,13 +256,20 @@ class InAppUpdater {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val update = getAppUpdate()
|
val update = getAppUpdate()
|
||||||
if (update.shouldUpdate && update.updateURL != null) {
|
if (
|
||||||
|
update.shouldUpdate &&
|
||||||
|
update.updateURL != null) {
|
||||||
|
|
||||||
// Check if update should be skipped
|
// Check if update should be skipped
|
||||||
val updateNodeId =
|
val updateNodeId =
|
||||||
settingsManager.getString(getString(R.string.skip_update_key), "")
|
settingsManager.getString(getString(R.string.skip_update_key), "")
|
||||||
if (update.updateNodeId.equals(updateNodeId)) {
|
|
||||||
|
// Skips the update if its an automatic update and the update is skipped
|
||||||
|
// This allows updating manually
|
||||||
|
if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
try {
|
try {
|
||||||
val currentVersion = packageName?.let {
|
val currentVersion = packageName?.let {
|
||||||
|
@ -283,16 +292,23 @@ class InAppUpdater {
|
||||||
builder.apply {
|
builder.apply {
|
||||||
setPositiveButton(R.string.update) { _, _ ->
|
setPositiveButton(R.string.update) { _, _ ->
|
||||||
showToast(context, R.string.download_started, Toast.LENGTH_LONG)
|
showToast(context, R.string.download_started, Toast.LENGTH_LONG)
|
||||||
ioSafe {
|
val intent = PackageInstallerService.getIntent(
|
||||||
if (!downloadUpdate(update.updateURL))
|
|
||||||
runOnUiThread {
|
|
||||||
showToast(
|
|
||||||
context,
|
context,
|
||||||
R.string.download_failed,
|
update.updateURL
|
||||||
Toast.LENGTH_LONG
|
|
||||||
)
|
)
|
||||||
}
|
ContextCompat.startForegroundService(context, intent)
|
||||||
}
|
// ioSafe {
|
||||||
|
// if (
|
||||||
|
// !downloadUpdate(update.updateURL)
|
||||||
|
// )
|
||||||
|
// runOnUiThread {
|
||||||
|
// showToast(
|
||||||
|
// context,
|
||||||
|
// R.string.download_failed,
|
||||||
|
// Toast.LENGTH_LONG
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
setNegativeButton(R.string.cancel) { _, _ -> }
|
setNegativeButton(R.string.cancel) { _, _ -> }
|
||||||
|
@ -302,8 +318,7 @@ class InAppUpdater {
|
||||||
settingsManager.edit().putString(
|
settingsManager.edit().putString(
|
||||||
getString(R.string.skip_update_key),
|
getString(R.string.skip_update_key),
|
||||||
update.updateNodeId ?: ""
|
update.updateNodeId ?: ""
|
||||||
)
|
).apply()
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.os.Build
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
const val INSTALL_ACTION = "ApkInstaller.INSTALL_ACTION"
|
||||||
|
|
||||||
|
|
||||||
|
class ApkInstaller(private val service: PackageInstallerService) {
|
||||||
|
private val packageInstaller = service.packageManager.packageInstaller
|
||||||
|
|
||||||
|
enum class InstallProgressStatus {
|
||||||
|
Preparing,
|
||||||
|
Downloading,
|
||||||
|
Installing,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
private val installActionReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (intent.getIntExtra(
|
||||||
|
PackageInstaller.EXTRA_STATUS,
|
||||||
|
PackageInstaller.STATUS_FAILURE
|
||||||
|
)) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
val userAction = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||||
|
userAction?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(userAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun installApk(
|
||||||
|
context: Context,
|
||||||
|
inputStream: InputStream,
|
||||||
|
size: Long,
|
||||||
|
installProgress: (bytesRead: Int) -> Unit,
|
||||||
|
installProgressStatus: (InstallProgressStatus) -> Unit
|
||||||
|
) {
|
||||||
|
installProgressStatus.invoke(InstallProgressStatus.Preparing)
|
||||||
|
var activeSession: Int? = null
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSession = packageInstaller.createSession(installParams)
|
||||||
|
installParams.setSize(size)
|
||||||
|
|
||||||
|
val session = packageInstaller.openSession(activeSession)
|
||||||
|
installProgressStatus.invoke(InstallProgressStatus.Downloading)
|
||||||
|
|
||||||
|
session.openWrite(context.packageName, 0, size)
|
||||||
|
.use { outputStream ->
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var bytesRead = inputStream.read(buffer)
|
||||||
|
|
||||||
|
while (bytesRead >= 0) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead)
|
||||||
|
bytesRead = inputStream.read(buffer)
|
||||||
|
installProgress.invoke(bytesRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
installProgressStatus.invoke(InstallProgressStatus.Installing)
|
||||||
|
|
||||||
|
val intentSender = PendingIntent.getBroadcast(
|
||||||
|
service,
|
||||||
|
activeSession,
|
||||||
|
Intent(INSTALL_ACTION),
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0,
|
||||||
|
).intentSender
|
||||||
|
|
||||||
|
session.commit(intentSender)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
|
||||||
|
service.unregisterReceiver(installActionReceiver)
|
||||||
|
installProgressStatus.invoke(InstallProgressStatus.Failed)
|
||||||
|
|
||||||
|
activeSession?.let { sessionId ->
|
||||||
|
packageInstaller.abandonSession(sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
service.registerReceiver(installActionReceiver, IntentFilter(INSTALL_ACTION))
|
||||||
|
service.receivers.add(installActionReceiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
|
class PackageInstallerService : Service() {
|
||||||
|
val receivers = mutableListOf<BroadcastReceiver>()
|
||||||
|
|
||||||
|
private val baseNotification by lazy {
|
||||||
|
val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_MUTABLE
|
||||||
|
} else 0
|
||||||
|
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
val pendingIntent =
|
||||||
|
PendingIntent.getActivity(this, 0, intent, flag)
|
||||||
|
|
||||||
|
NotificationCompat.Builder(this, UPDATE_CHANNEL_ID)
|
||||||
|
.setAutoCancel(false)
|
||||||
|
.setColorized(true)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setSilent(true)
|
||||||
|
// If low priority then the notification might not show :(
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setColor(this.colorFromAttribute(R.attr.colorPrimary))
|
||||||
|
.setContentTitle(getString(R.string.update_notification_downloading))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setSmallIcon(R.drawable.rdload)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
val channel =
|
||||||
|
NotificationChannel(UPDATE_CHANNEL_ID, UPDATE_CHANNEL_NAME, importance).apply {
|
||||||
|
description = UPDATE_CHANNEL_DESCRIPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the channel with the system
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
createNotificationChannel()
|
||||||
|
startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val updateLock = Mutex()
|
||||||
|
|
||||||
|
private suspend fun downloadUpdate(url: String): Boolean {
|
||||||
|
try {
|
||||||
|
Log.d(LOG_TAG, "Downloading update: $url")
|
||||||
|
|
||||||
|
// Delete all old updates
|
||||||
|
ioSafe {
|
||||||
|
val appUpdateName = "CloudStream"
|
||||||
|
val appUpdateSuffix = "apk"
|
||||||
|
|
||||||
|
this@PackageInstallerService.cacheDir.listFiles()?.filter {
|
||||||
|
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
|
||||||
|
}?.forEach {
|
||||||
|
it.deleteOnExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLock.withLock {
|
||||||
|
updateNotificationProgress(
|
||||||
|
0f,
|
||||||
|
ApkInstaller.InstallProgressStatus.Downloading
|
||||||
|
)
|
||||||
|
|
||||||
|
val body = app.get(url).body
|
||||||
|
val inputStream = body.byteStream()
|
||||||
|
val installer = ApkInstaller(this)
|
||||||
|
val totalSize = body.contentLength()
|
||||||
|
var currentSize = 0
|
||||||
|
|
||||||
|
installer.installApk(this, inputStream, totalSize, {
|
||||||
|
currentSize += it
|
||||||
|
// Prevent div 0
|
||||||
|
if (totalSize == 0L) return@installApk
|
||||||
|
|
||||||
|
val percentage = currentSize / totalSize.toFloat()
|
||||||
|
updateNotificationProgress(
|
||||||
|
percentage,
|
||||||
|
ApkInstaller.InstallProgressStatus.Downloading
|
||||||
|
)
|
||||||
|
}) { status ->
|
||||||
|
updateNotificationProgress(0f, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
updateNotificationProgress(0f, ApkInstaller.InstallProgressStatus.Failed)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotificationProgress(
|
||||||
|
percentage: Float,
|
||||||
|
state: ApkInstaller.InstallProgressStatus
|
||||||
|
) {
|
||||||
|
// Log.d(LOG_TAG, "Downloading app update progress $percentage | $state")
|
||||||
|
val text = when (state) {
|
||||||
|
ApkInstaller.InstallProgressStatus.Installing -> R.string.update_notification_installing
|
||||||
|
ApkInstaller.InstallProgressStatus.Preparing, ApkInstaller.InstallProgressStatus.Downloading -> R.string.update_notification_downloading
|
||||||
|
ApkInstaller.InstallProgressStatus.Failed -> R.string.update_notification_failed
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNotification = baseNotification
|
||||||
|
.setContentTitle(getString(text))
|
||||||
|
.apply {
|
||||||
|
if (state == ApkInstaller.InstallProgressStatus.Failed) {
|
||||||
|
setSmallIcon(R.drawable.rderror)
|
||||||
|
setAutoCancel(true)
|
||||||
|
} else {
|
||||||
|
setProgress(
|
||||||
|
10000, (10000 * percentage).roundToInt(),
|
||||||
|
state != ApkInstaller.InstallProgressStatus.Downloading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val notificationManager =
|
||||||
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
// Persistent notification on failure
|
||||||
|
val id =
|
||||||
|
if (state == ApkInstaller.InstallProgressStatus.Failed) UPDATE_NOTIFICATION_ID + 1 else UPDATE_NOTIFICATION_ID
|
||||||
|
notificationManager.notify(id, newNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
val url = intent?.getStringExtra(EXTRA_URL) ?: return START_NOT_STICKY
|
||||||
|
ioSafe {
|
||||||
|
downloadUpdate(url)
|
||||||
|
// Close the service after the update is done
|
||||||
|
// If no sleep then the install prompt may not appear and the notification
|
||||||
|
// will disappear instantly
|
||||||
|
delay(10_000)
|
||||||
|
this@PackageInstallerService.stopSelf()
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
receivers.forEach {
|
||||||
|
try {
|
||||||
|
this.unregisterReceiver(it)
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
// Receiver not registered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(i: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_URL = "EXTRA_URL"
|
||||||
|
private const val LOG_TAG = "PackageInstallerService"
|
||||||
|
|
||||||
|
const val UPDATE_CHANNEL_ID = "cloudstream3.updates"
|
||||||
|
const val UPDATE_CHANNEL_NAME = "App Updates"
|
||||||
|
const val UPDATE_CHANNEL_DESCRIPTION = "App updates notification channel"
|
||||||
|
const val UPDATE_NOTIFICATION_ID = -68454136
|
||||||
|
|
||||||
|
fun getIntent(
|
||||||
|
context: Context,
|
||||||
|
url: String,
|
||||||
|
): Intent {
|
||||||
|
return Intent(context, PackageInstallerService::class.java)
|
||||||
|
.putExtra(EXTRA_URL, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -659,4 +659,8 @@
|
||||||
<string name="confirm_exit_dialog">Are you sure you want to exit?</string>
|
<string name="confirm_exit_dialog">Are you sure you want to exit?</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
|
|
||||||
|
<string name="update_notification_downloading">Downloading app update</string>
|
||||||
|
<string name="update_notification_installing">Installing app update</string>
|
||||||
|
<string name="update_notification_failed">App update failed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue