Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In-App Updating Option (Closes #121) #133

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
android:name=".RaceControlTvApplication"
Expand All @@ -21,6 +22,16 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>

<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
Expand Down
100 changes: 100 additions & 0 deletions app/src/main/java/fr/groggy/racecontrol/tv/ui/home/HomeActivity.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
package fr.groggy.racecontrol.tv.ui.home

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import fr.groggy.racecontrol.tv.BuildConfig
import fr.groggy.racecontrol.tv.R
import fr.groggy.racecontrol.tv.core.season.SeasonService
import fr.groggy.racecontrol.tv.f1tv.Archive
import fr.groggy.racecontrol.tv.ui.season.browse.SeasonBrowseActivity
import fr.groggy.racecontrol.tv.ui.settings.SettingsActivity
import fr.groggy.racecontrol.tv.upd.DownloadApk
import fr.groggy.racecontrol.tv.utils.coroutines.schedule
import org.threeten.bp.Duration
import org.threeten.bp.Year
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.net.URL
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection


@AndroidEntryPoint
class HomeActivity : FragmentActivity(R.layout.activity_home) {
Expand All @@ -34,6 +45,7 @@ class HomeActivity : FragmentActivity(R.layout.activity_home) {
@Inject internal lateinit var seasonService: SeasonService
private var teaserImage: ImageView? = null

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -50,6 +62,94 @@ class HomeActivity : FragmentActivity(R.layout.activity_home) {
findViewById<View>(R.id.settings).setOnClickListener {
startActivity(SettingsActivity.intent(this))
}

val currentVersion = BuildConfig.VERSION_NAME // Getting current version (eg. comparing with Version)
// val currentVersion = "2.6.2" // Debug
val currentVersionText: TextView = findViewById<TextView>(R.id.currentVersionText)
currentVersionText.text = currentVersion // Showing Version number top left

AsyncTask.execute {

val httpsURL =
"https://raw.githubusercontent.com/leonardoxh/race-control-tv/master/app/build.gradle.kts"
val myUrl = URL(httpsURL)
Copy link
Owner

Choose a reason for hiding this comment

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

Can you move this entire thing to a separate class like updateService ?

Copy link
Owner

Choose a reason for hiding this comment

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

And also use coroutines to handle this network part.

Copy link
Author

Choose a reason for hiding this comment

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

Sure, I can do that.
I thought about planning it like this: Merging and publishing a new release with this current version of the updater so the users have a general update service, so they don't need to manually sideload the new version every time.

After this I'll open a new PR to refine the code and put them into another class and also convert the ThreadWorker to Coroutines. This then can be updated into another release when there are other significant changes to the app.

Do we wanna do it like this?

Copy link
Owner

Choose a reason for hiding this comment

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

I think it is better to put all of this in 1 PR.

Because this async task work and manual work seems like 2010 :D Also AsyncTask is deprecated...

Copy link
Owner

Choose a reason for hiding this comment

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

And second... As the app is pretty much dead (no live content yet) I'd say no rush on this one.

Copy link
Author

@LoVega1337 LoVega1337 May 9, 2022

Choose a reason for hiding this comment

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

Okay so I have played around with Coroutines a bit and everything I try fails, so I need to do some research on this, since I only worked on small projects and with Async tasks so far. So this might take a while.

val conn: HttpsURLConnection = myUrl.openConnection() as HttpsURLConnection
val `is`: InputStream = conn.getInputStream()
val isr = InputStreamReader(`is`)
val br = BufferedReader(isr)
var inputLine: String?
var completeResponse = ""

while (br.readLine().also { inputLine = it } != null) {
Log.d("inputResponse", inputLine.toString())
completeResponse += inputLine
}

br.close()

completeResponse =
completeResponse.substring(completeResponse.indexOf("versionName = \"") + 15)
completeResponse = completeResponse.substring(
0,
completeResponse.indexOf("\" buildConfigField")
)

Log.d("CompleteResponse", completeResponse)

if (completeResponse != currentVersion) {
Log.d("@@@@@", "Different Version")
runOnUiThread {
var newVersionText: TextView? = null
newVersionText = findViewById(R.id.newVersionText)
newVersionText.text = "${this.getResources().getString(R.string.new_version_available)}: $completeResponse!"
newVersionText.visibility = View.VISIBLE
}
}

}

findViewById<View>(R.id.updateApp).setOnClickListener {

AsyncTask.execute {

val httpsURL = "https://raw.githubusercontent.com/leonardoxh/race-control-tv/master/app/build.gradle.kts"
val myUrl = URL(httpsURL)
val conn: HttpsURLConnection = myUrl.openConnection() as HttpsURLConnection
val `is`: InputStream = conn.getInputStream()
val isr = InputStreamReader(`is`)
val br = BufferedReader(isr)
var inputLine: String?
var completeResponse = ""

while (br.readLine().also { inputLine = it } != null) {
Log.d("inputResponse", inputLine.toString())
completeResponse += inputLine
}

br.close()

completeResponse = completeResponse.substring(completeResponse.indexOf("versionName = \"") + 15)
completeResponse = completeResponse.substring(0, completeResponse.indexOf("\" buildConfigField"))

Log.d("CompleteResponse", completeResponse)

if (completeResponse == currentVersion) {
Log.d("@@@@@", "Same Version")
runOnUiThread {
Toast.makeText(this, R.string.no_update_available, Toast.LENGTH_SHORT).show()
}
}
else {
Log.d("@@@@@", "Different Version")

runOnUiThread {
val url = "https://github.com/leonardoxh/race-control-tv/releases/latest/download/app-release.apk"
val downloadApk = DownloadApk(this@HomeActivity)
downloadApk.startDownloadingApk(url)
}
}
}
}
}

override fun onStart() {
Expand Down
149 changes: 149 additions & 0 deletions app/src/main/java/fr/groggy/racecontrol/tv/upd/DownloadApk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package fr.groggy.racecontrol.tv.upd

import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Environment
import android.util.Log
import android.webkit.URLUtil
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import fr.groggy.racecontrol.tv.R
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URL


class DownloadApk(var context: Context) : AppCompatActivity() {

@JvmOverloads
fun startDownloadingApk(url: String, fileName: String = "RaceControlTV") {
if (URLUtil.isValidUrl(url)) {
DownloadNewVersion(context, url, fileName).execute()
}
}

@Suppress("DEPRECATION")
private class DownloadNewVersion(
val context: Context,
val downloadUrl: String,
val fileName: String
): AsyncTask<String, Int, Boolean>() {
private lateinit var bar: ProgressDialog
override fun onPreExecute() {
super.onPreExecute()
bar = ProgressDialog(context).apply {
setCancelable(false)
setMessage(context.getResources().getString(R.string.update_downloading))
isIndeterminate = true
setCanceledOnTouchOutside(false)
show()
}
}

override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
var msg = ""
val progress = values[0]
if (progress != null) {
bar.progress = progress
msg = if (progress > 99) context.getResources().getString(R.string.update_finishing) else "${context.getResources().getString(R.string.update_downloading)} $progress%"
}

bar.apply {
isIndeterminate = false
max = 100
setMessage(msg)
}
}

override fun onPostExecute(result: Boolean?) {
super.onPostExecute(result)
bar.dismiss()
if (result != null && result) {
Toast.makeText(context, R.string.update_downloaded, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, R.string.update_error, Toast.LENGTH_SHORT).show()
}
}

override fun doInBackground(vararg p0: String?): Boolean {
var flag = false

try {
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/"
var outputFile = File("$path$fileName.apk")
if (outputFile.exists()) {
outputFile.delete()
outputFile = File("$path$fileName.apk")
}

val directory = File(path)
if (!directory.exists()) {
directory.mkdirs()
}

val url = URL(downloadUrl)
val c = url.openConnection() as HttpURLConnection
c.requestMethod = "GET"
c.connect()

val fos = FileOutputStream(outputFile)
val inputStream = c.inputStream
val totalSize = c.contentLength.toFloat() //size of apk

val buffer = ByteArray(1024)
var len1: Int
var per: Float
var downloaded = 0f
while (inputStream.read(buffer).also { len1 = it } != -1) {
fos.write(buffer, 0, len1)
downloaded += len1
per = (downloaded * 100 / totalSize)
publishProgress(per.toInt())
}
fos.close()
inputStream.close()
openNewVersion(outputFile.path)
flag = true
} catch (e: MalformedURLException) {
Log.e("DownloadApk", "Update Error: " + e.message)
flag = false
} catch (e: IOException) {
e.printStackTrace()
}

return flag
}

private fun openNewVersion(location: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
getUriFromFile(location),
"application/vnd.android.package-archive"
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
}

private fun getUriFromFile(filePath: String): Uri {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Uri.fromFile(File(filePath))
} else {
FileProvider.getUriForFile(
context,
context.packageName + ".provider",
File(filePath)
)
}
}
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_baseline_system_update_alt_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,16.5l4,-4h-3v-9h-2v9L8,12.5l4,4zM21,3.5h-6v1.99h6v14.03L3,19.52L3,5.49h6L9,3.5L3,3.5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2v-14c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>
Loading