support for silent updates

This commit is contained in:
Bnyro 2022-07-18 19:15:35 +02:00
parent e48da1109a
commit ebcd11d00c
9 changed files with 137 additions and 50 deletions

View File

@ -277,8 +277,12 @@
<service
android:name=".services.ClosingService"
android:enabled="true"
android:exported="false"
android:stopWithTask="false" />
android:exported="false" />
<service
android:name=".services.UpdateService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@ -4,6 +4,8 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import com.github.libretube.preferences.PreferenceHelper
class MyApp : Application() {
@ -19,6 +21,12 @@ class MyApp : Application() {
* set the applicationContext as context for the [PreferenceHelper]
*/
PreferenceHelper.setContext(applicationContext)
/**
* bypassing fileUriExposedException, see https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed
*/
val builder = VmPolicy.Builder()
StrictMode.setVmPolicy(builder.build())
}
/**

View File

@ -3,43 +3,51 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.DialogFragment
import com.github.libretube.R
import com.github.libretube.services.UpdateService
import com.github.libretube.update.UpdateInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class UpdateAvailableDialog(
private val versionTag: String,
private val updateLink: String
class UpdateDialog(
private val updateInfo: UpdateInfo
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
MaterialAlertDialogBuilder(requireContext())
.setTitle(context?.getString(R.string.update_available, versionTag))
.setMessage(context?.getString(R.string.update_available_text))
.setTitle(context?.getString(R.string.update_available, updateInfo.name))
.setMessage(context?.getString(R.string.update_now))
.setNegativeButton(context?.getString(R.string.cancel)) { _, _ ->
dismiss()
}
.setPositiveButton(context?.getString(R.string.okay)) { _, _ ->
val uri = Uri.parse(updateLink)
val downloadUrl = getDownloadUrl(updateInfo)
Log.i("downloadUrl", downloadUrl.toString())
if (downloadUrl != null) {
val intent = Intent(context, UpdateService::class.java)
intent.putExtra("downloadUrl", downloadUrl)
context?.startService(intent)
} else {
val uri = Uri.parse(updateInfo.html_url)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
}
}
.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
class NoUpdateAvailableDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
MaterialAlertDialogBuilder(requireContext())
.setTitle(context?.getString(R.string.app_uptodate))
.setMessage(context?.getString(R.string.no_update_available))
.setPositiveButton(context?.getString(R.string.okay)) { _, _ -> }
.create()
} ?: throw IllegalStateException("Activity cannot be null")
private fun getDownloadUrl(updateInfo: UpdateInfo): String? {
val supportedArchitectures = Build.SUPPORTED_ABIS
supportedArchitectures.forEach { arch ->
updateInfo.assets.forEach {
if (it.browser_download_url.contains(arch)) return it.browser_download_url
}
}
return null
}
}

View File

@ -1,7 +0,0 @@
package com.github.libretube.obj
// data class for the update info, required to return the data
data class VersionInfo(
val updateUrl: String,
val tagName: String
)

View File

@ -10,9 +10,9 @@ import com.github.libretube.BuildConfig
import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.dialogs.RequireRestartDialog
import com.github.libretube.dialogs.UpdateAvailableDialog
import com.github.libretube.dialogs.UpdateDialog
import com.github.libretube.update.checkUpdate
import com.github.libretube.util.ThemeHelper
import com.github.libretube.util.checkUpdate
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -93,13 +93,10 @@ class MainSettings : PreferenceFragmentCompat() {
update?.setOnPreferenceClickListener {
CoroutineScope(Dispatchers.IO).launch {
// check for update
val versionInfo = checkUpdate()
if (versionInfo?.tagName != "" && BuildConfig.VERSION_NAME != versionInfo?.tagName) {
val updateInfo = checkUpdate()
if (updateInfo?.name != "" && BuildConfig.VERSION_NAME != updateInfo?.name) {
// show the UpdateAvailableDialog if there's an update available
val updateAvailableDialog = UpdateAvailableDialog(
versionInfo!!.tagName,
versionInfo.updateUrl
)
val updateAvailableDialog = UpdateDialog(updateInfo!!)
updateAvailableDialog.show(childFragmentManager, "UpdateAvailableDialog")
} else {
// otherwise show the no update available snackBar

View File

@ -164,7 +164,10 @@ class DownloadService : Service() {
onDestroy()
}
} else {
try {
muxDownloadedMedia()
} catch (e: Exception) {
}
}
}
}

View File

@ -0,0 +1,79 @@
package com.github.libretube.services
import android.app.DownloadManager
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import android.os.IBinder
import java.io.File
class UpdateService : Service() {
private val TAG = "UpdateService"
private lateinit var downloadUrl: String
private var downloadId: Long = -1
private lateinit var file: File
private lateinit var downloadManager: DownloadManager
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
downloadUrl = intent?.getStringExtra("downloadUrl")!!
downloadApk(downloadUrl)
return super.onStartCommand(intent, flags, startId)
}
private fun downloadApk(downloadUrl: String) {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
// val dir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
file = File(dir, "release.apk")
val request: DownloadManager.Request =
DownloadManager.Request(Uri.parse(downloadUrl))
.setTitle("Downloading APK ...")
.setDescription("")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationUri(Uri.fromFile(file))
.setAllowedOverMetered(true)
.setAllowedOverRoaming(true)
downloadManager =
applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request)
// listener for the download to end
registerReceiver(
onDownloadComplete,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
}
private val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
// install the apk after download finished
val installIntent = Intent(Intent.ACTION_VIEW)
installIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
installIntent.setDataAndType(
Uri.fromFile(file),
downloadManager.getMimeTypeForDownloadedFile(downloadId)
)
startActivity(installIntent)
}
}
}
override fun onDestroy() {
unregisterReceiver(onDownloadComplete)
super.onDestroy()
}
override fun onBind(p0: Intent?): IBinder? {
TODO("Not yet implemented")
}
}

View File

@ -1,16 +1,14 @@
package com.github.libretube.util
package com.github.libretube.update
import com.github.libretube.GITHUB_API_URL
import com.github.libretube.obj.VersionInfo
import com.github.libretube.update.UpdateInfo
import com.google.gson.Gson
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import javax.net.ssl.HttpsURLConnection
fun checkUpdate(): VersionInfo? {
var versionInfo: VersionInfo? = null
fun checkUpdate(): UpdateInfo? {
var versionInfo: UpdateInfo? = null
// run http request as thread to make it async
val thread = Thread {
// otherwise crashes without internet
@ -27,22 +25,18 @@ fun checkUpdate(): VersionInfo? {
return versionInfo
}
fun getUpdateInfo(): VersionInfo? {
fun getUpdateInfo(): UpdateInfo? {
val latest = URL(GITHUB_API_URL)
val json = StringBuilder()
val urlConnection: HttpsURLConnection?
urlConnection = latest.openConnection() as HttpsURLConnection
val br = BufferedReader(InputStreamReader(urlConnection.inputStream))
// read json
val br = BufferedReader(InputStreamReader(urlConnection.inputStream))
var line: String?
while (br.readLine().also { line = it } != null) json.append(line)
// Parse and return the json data
val gson = Gson()
val updateInfo = gson.fromJson(json.toString(), UpdateInfo::class.java)
return VersionInfo(
updateInfo.html_url,
updateInfo.name
)
return gson.fromJson(json.toString(), UpdateInfo::class.java)
}

View File

@ -252,4 +252,5 @@
<string name="system_caption_style">System caption style</string>
<string name="captions">Captions</string>
<string name="none">None</string>
<string name="update_now">Do you want to update the app now?</string>
</resources>