feat: auto updater (#23)

* feature: auto updater

* fix: formatting

* feat: auto updater

---------

Co-authored-by: auth <64337177+authorisation@users.noreply.github.com>
This commit is contained in:
rhunk 2023-06-04 23:39:03 +02:00 committed by GitHub
parent 39769f8940
commit 8d36ae8799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 220 additions and 6 deletions

View File

@ -3,6 +3,7 @@
"spying_privacy": "Spying & Privacy",
"media_manager": "Media Manager",
"ui_tweaks": "UI & Tweaks",
"updates": "Updates",
"experimental_debugging": "Experimental"
},
@ -10,7 +11,8 @@
"clean_cache": "Clean Cache",
"clear_message_logger": "Clear Message Logger",
"refresh_mappings": "Refresh Mappings",
"open_map": "Choose location on map"
"open_map": "Choose location on map",
"check_for_updates": "Check for updates"
},
"property": {
@ -55,7 +57,8 @@
"location_spoof": "Snapmap Location Spoofer",
"latitude_value": "Latitude",
"longitude_value": "Longitude",
"hide_ui_elements": "Hide UI Elements"
"hide_ui_elements": "Hide UI Elements",
"auto_updater": "Auto Updater"
},
"option": {
@ -112,6 +115,12 @@
"remove_cognac_button": "Remove Cognac Button",
"remove_stickers_button": "Remove Stickers Button",
"remove_voice_record_button": "Remove Voice Record Button"
},
"auto_updater": {
"DISABLED": "Disabled",
"EVERY_LAUNCH": "Every Launch",
"DAILY": "Daily",
"WEEKLY": "Weekly"
}
}
},
@ -155,5 +164,15 @@
"display_name": "Display Name",
"added_date": "Added Date",
"birthday": "Birthday : {month} {day}"
},
"auto_updater": {
"no_update_available": "No Update available!",
"dialog_title": "New Update available!",
"dialog_message": "There is a new Update for SnapEnhance available! ({version})\n\n{body}",
"dialog_positive_button": "Download and Install",
"dialog_negative_button": "Cancel",
"downloading_toast": "Downloading Update...",
"download_manager_notification_title": "Downloading SnapEnhance APK..."
}
}

View File

@ -0,0 +1,20 @@
package me.rhunk.snapenhance.action.impl
import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.impl.AutoUpdater
class CheckForUpdates : AbstractAction("action.check_for_updates", dependsOnProperty = ConfigProperty.AUTO_UPDATER) {
override fun run() {
context.executeAsync {
runCatching {
val latestVersion = context.feature(AutoUpdater::class).checkForUpdates()
if (latestVersion == null) {
context.longToast(context.translation.get("auto_updater.no_update_available"))
}
}.onFailure {
context.longToast(it.message ?: "Failed to check for updates")
}
}
}
}

View File

@ -99,4 +99,16 @@ abstract class AbstractBridgeClient {
* @return the translations result
*/
abstract fun fetchTranslations(): LocaleResult
/**
* Get check for updates last time
* @return the last time check for updates was done
*/
abstract fun getAutoUpdaterTime(): Long
/**
* Set check for updates last time
* @param time the time to set
*/
abstract fun setAutoUpdaterTime(time: Long)
}

View File

@ -112,6 +112,20 @@ class RootBridgeClient : AbstractBridgeClient() {
throw Throwable("Failed to fetch translations for $locale")
}
override fun getAutoUpdaterTime(): Long {
readFile(BridgeFileType.ANTI_AUTO_DOWNLOAD).run {
return if (isEmpty()) {
0
} else {
String(this).toLong()
}
}
}
override fun setAutoUpdaterTime(time: Long) {
writeFile(BridgeFileType.AUTO_UPDATER_TIMESTAMP, time.toString().toByteArray(Charsets.UTF_8))
}
private fun rootOperation(command: String): String {
val process = Runtime.getRuntime().exec("su -c $command")
process.waitFor()

View File

@ -221,6 +221,20 @@ class ServiceBridgeClient: AbstractBridgeClient(), ServiceConnection {
}
}
override fun getAutoUpdaterTime(): Long {
readFile(BridgeFileType.ANTI_AUTO_DOWNLOAD).run {
return if (isEmpty()) {
0
} else {
String(this).toLong()
}
}
}
override fun setAutoUpdaterTime(time: Long) {
writeFile(BridgeFileType.ANTI_AUTO_DOWNLOAD, time.toString().toByteArray())
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
messenger = Messenger(service)
future.complete(true)

View File

@ -7,7 +7,8 @@ enum class BridgeFileType(val value: Int, val fileName: String, val isDatabase:
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", true),
STEALTH(3, "stealth.txt"),
ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt"),
ANTI_AUTO_SAVE(5, "anti_auto_save.txt");
ANTI_AUTO_SAVE(5, "anti_auto_save.txt"),
AUTO_UPDATER_TIMESTAMP(6, "auto_updater_timestamp.txt");
companion object {
fun fromValue(value: Int): BridgeFileType? {

View File

@ -6,5 +6,6 @@ enum class ConfigCategory(
SPYING_PRIVACY("category.spying_privacy"),
MEDIA_MANAGEMENT("category.media_manager"),
UI_TWEAKS("category.ui_tweaks"),
UPDATES("category.updates"),
EXPERIMENTAL_DEBUGGING("category.experimental_debugging");
}

View File

@ -257,6 +257,17 @@ enum class ConfigProperty(
ConfigCategory.UI_TWEAKS,
ConfigIntegerValue(20)
),
// UPDATES
AUTO_UPDATER(
"property.auto_updater",
"description.auto_updater",
ConfigCategory.UPDATES,
ConfigStateSelection(
listOf("DISABLED", "EVERY_LAUNCH", "DAILY", "WEEKLY"),
"DAILY"
)
),
// EXPERIMENTAL DEBUGGING
USE_DOWNLOAD_MANAGER(

View File

@ -0,0 +1,109 @@
package me.rhunk.snapenhance.features.impl
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.DownloadManager
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 me.rhunk.snapenhance.BuildConfig
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
override fun asyncOnActivityCreate() {
val checkForUpdateMode = context.config.state(ConfigProperty.AUTO_UPDATER)
val currentTimeMillis = System.currentTimeMillis()
val checkForUpdatesTimestamp = context.bridgeClient.getAutoUpdaterTime()
val delayTimestamp = when (checkForUpdateMode) {
"EVERY_LAUNCH" -> currentTimeMillis - checkForUpdatesTimestamp
"DAILY" -> 86400000L
"WEEKLY" -> 604800000L
else -> return
}
if (checkForUpdatesTimestamp + delayTimestamp > currentTimeMillis) return
runCatching {
checkForUpdates()
}.onFailure {
Logger.error("Failed to check for updates: ${it.message}", it)
}.onSuccess {
context.bridgeClient.setAutoUpdaterTime(currentTimeMillis)
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun checkForUpdates(): String? {
val endpoint = Request.Builder().url("https://api.github.com/repos/rhunk/SnapEnhance/releases").build()
val response = OkHttpClient().newCall(endpoint).execute()
if (!response.isSuccessful) throw Throwable("Failed to fetch releases: ${response.code}")
val releases = JSONArray(response.body.string()).also {
if (it.length() == 0) throw Throwable("No releases found")
}
val latestRelease = releases.getJSONObject(0)
val latestVersion = latestRelease.getString("tag_name")
if (latestVersion.removePrefix("v") == BuildConfig.VERSION_NAME) return null
val releaseContentBody = latestRelease.getString("body")
val downloadEndpoint = latestRelease.getJSONArray("assets").getJSONObject(0).getString("browser_download_url")
context.runOnUiThread {
AlertDialog.Builder(context.mainActivity)
.setTitle(context.translation.get("auto_updater.dialog_title"))
.setMessage(
context.translation.get("auto_updater.dialog_message")
.replace("{version}", latestVersion)
.replace("{body}", releaseContentBody)
)
.setNegativeButton(context.translation.get("auto_updater.dialog_negative_button")) { dialog, _ ->
dialog.dismiss()
}
.setPositiveButton(context.translation.get("auto_updater.dialog_positive_button")) { dialog, _ ->
dialog.dismiss()
context.longToast(context.translation.get("auto_updater.downloading_toast"))
val request = DownloadManager.Request(Uri.parse(downloadEndpoint))
.setTitle(context.translation.get("auto_updater.download_manager_notification_title"))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "latest-snapenhance.apk")
.setMimeType("application/vnd.android.package-archive")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
val downloadManager = context.androidContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadId = downloadManager.enqueue(request)
val onCompleteReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (id != downloadId) return
context.unregisterReceiver(this)
context.startActivity(
Intent(Intent.ACTION_VIEW).apply {
setDataAndType(downloadManager.getUriForDownloadedFile(downloadId), "application/vnd.android.package-archive")
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
)
}
}
context.mainActivity?.registerReceiver(onCompleteReceiver, IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE
))
}.show()
}
return latestVersion
}
}

View File

@ -2,11 +2,13 @@ package me.rhunk.snapenhance.features.impl.ui.menus.impl
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.graphics.Color
import android.graphics.Typeface
import android.text.InputType
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Switch
import android.widget.TextView
import me.rhunk.snapenhance.BuildConfig
@ -163,6 +165,13 @@ class SettingsMenu : AbstractMenu() {
return resultView
}
private fun newSeparator(thickness: Int, color: Int = Color.BLACK): View {
return LinearLayout(context.mainActivity).apply {
setPadding(0, 0, 0, thickness)
setBackgroundColor(color)
}
}
@SuppressLint("SetTextI18n")
@Suppress("deprecation")
fun inject(viewModel: View, addView: (View) -> Unit) {
@ -206,9 +215,10 @@ class SettingsMenu : AbstractMenu() {
}
}
//addView(createCategoryTitle(viewModel, "category.debugging"))
actions.filter { it.first.dependsOnProperty == null }.forEach {
addView(it.second())
}
addView(newSeparator(3, Color.parseColor("#f5f5f5")))
}
}

View File

@ -2,6 +2,7 @@ package me.rhunk.snapenhance.manager.impl
import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.action.impl.CheckForUpdates
import me.rhunk.snapenhance.action.impl.CleanCache
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
import me.rhunk.snapenhance.action.impl.OpenMap
@ -24,6 +25,7 @@ class ActionManager(
load(ClearMessageLogger::class)
load(RefreshMappings::class)
load(OpenMap::class)
load(CheckForUpdates::class)
actions.values.forEach(AbstractAction::init)
}

View File

@ -4,6 +4,7 @@ import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.features.impl.AutoUpdater
import me.rhunk.snapenhance.features.impl.ConfigEnumKeys
import me.rhunk.snapenhance.features.impl.Messaging
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
@ -75,7 +76,7 @@ class FeatureManager(private val context: ModContext) : Manager {
register(MeoPasscodeBypass::class)
register(AppPasscode::class)
register(LocationSpoofer::class)
register(AutoUpdater::class)
initializeFeatures()
}

View File

@ -74,7 +74,7 @@ class MappingManager(private val context: ModContext) : Manager {
val loadingDialog = statusDialogBuilder.show()
thread(start = true) {
context.executeAsync {
runCatching {
refresh()
}.onSuccess {