mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 12:30:12 +02:00
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:
parent
39769f8940
commit
8d36ae8799
@ -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..."
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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? {
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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")))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class MappingManager(private val context: ModContext) : Manager {
|
||||
|
||||
val loadingDialog = statusDialogBuilder.show()
|
||||
|
||||
thread(start = true) {
|
||||
context.executeAsync {
|
||||
runCatching {
|
||||
refresh()
|
||||
}.onSuccess {
|
||||
|
Loading…
x
Reference in New Issue
Block a user