mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 21:10:20 +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",
|
"spying_privacy": "Spying & Privacy",
|
||||||
"media_manager": "Media Manager",
|
"media_manager": "Media Manager",
|
||||||
"ui_tweaks": "UI & Tweaks",
|
"ui_tweaks": "UI & Tweaks",
|
||||||
|
"updates": "Updates",
|
||||||
"experimental_debugging": "Experimental"
|
"experimental_debugging": "Experimental"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -10,7 +11,8 @@
|
|||||||
"clean_cache": "Clean Cache",
|
"clean_cache": "Clean Cache",
|
||||||
"clear_message_logger": "Clear Message Logger",
|
"clear_message_logger": "Clear Message Logger",
|
||||||
"refresh_mappings": "Refresh Mappings",
|
"refresh_mappings": "Refresh Mappings",
|
||||||
"open_map": "Choose location on map"
|
"open_map": "Choose location on map",
|
||||||
|
"check_for_updates": "Check for updates"
|
||||||
},
|
},
|
||||||
|
|
||||||
"property": {
|
"property": {
|
||||||
@ -55,7 +57,8 @@
|
|||||||
"location_spoof": "Snapmap Location Spoofer",
|
"location_spoof": "Snapmap Location Spoofer",
|
||||||
"latitude_value": "Latitude",
|
"latitude_value": "Latitude",
|
||||||
"longitude_value": "Longitude",
|
"longitude_value": "Longitude",
|
||||||
"hide_ui_elements": "Hide UI Elements"
|
"hide_ui_elements": "Hide UI Elements",
|
||||||
|
"auto_updater": "Auto Updater"
|
||||||
},
|
},
|
||||||
|
|
||||||
"option": {
|
"option": {
|
||||||
@ -112,6 +115,12 @@
|
|||||||
"remove_cognac_button": "Remove Cognac Button",
|
"remove_cognac_button": "Remove Cognac Button",
|
||||||
"remove_stickers_button": "Remove Stickers Button",
|
"remove_stickers_button": "Remove Stickers Button",
|
||||||
"remove_voice_record_button": "Remove Voice Record 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",
|
"display_name": "Display Name",
|
||||||
"added_date": "Added Date",
|
"added_date": "Added Date",
|
||||||
"birthday": "Birthday : {month} {day}"
|
"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
|
* @return the translations result
|
||||||
*/
|
*/
|
||||||
abstract fun fetchTranslations(): LocaleResult
|
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")
|
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 {
|
private fun rootOperation(command: String): String {
|
||||||
val process = Runtime.getRuntime().exec("su -c $command")
|
val process = Runtime.getRuntime().exec("su -c $command")
|
||||||
process.waitFor()
|
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) {
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
messenger = Messenger(service)
|
messenger = Messenger(service)
|
||||||
future.complete(true)
|
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),
|
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", true),
|
||||||
STEALTH(3, "stealth.txt"),
|
STEALTH(3, "stealth.txt"),
|
||||||
ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.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 {
|
companion object {
|
||||||
fun fromValue(value: Int): BridgeFileType? {
|
fun fromValue(value: Int): BridgeFileType? {
|
||||||
|
@ -6,5 +6,6 @@ enum class ConfigCategory(
|
|||||||
SPYING_PRIVACY("category.spying_privacy"),
|
SPYING_PRIVACY("category.spying_privacy"),
|
||||||
MEDIA_MANAGEMENT("category.media_manager"),
|
MEDIA_MANAGEMENT("category.media_manager"),
|
||||||
UI_TWEAKS("category.ui_tweaks"),
|
UI_TWEAKS("category.ui_tweaks"),
|
||||||
|
UPDATES("category.updates"),
|
||||||
EXPERIMENTAL_DEBUGGING("category.experimental_debugging");
|
EXPERIMENTAL_DEBUGGING("category.experimental_debugging");
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,17 @@ enum class ConfigProperty(
|
|||||||
ConfigCategory.UI_TWEAKS,
|
ConfigCategory.UI_TWEAKS,
|
||||||
ConfigIntegerValue(20)
|
ConfigIntegerValue(20)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// UPDATES
|
||||||
|
AUTO_UPDATER(
|
||||||
|
"property.auto_updater",
|
||||||
|
"description.auto_updater",
|
||||||
|
ConfigCategory.UPDATES,
|
||||||
|
ConfigStateSelection(
|
||||||
|
listOf("DISABLED", "EVERY_LAUNCH", "DAILY", "WEEKLY"),
|
||||||
|
"DAILY"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
// EXPERIMENTAL DEBUGGING
|
// EXPERIMENTAL DEBUGGING
|
||||||
USE_DOWNLOAD_MANAGER(
|
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.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.Switch
|
import android.widget.Switch
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import me.rhunk.snapenhance.BuildConfig
|
import me.rhunk.snapenhance.BuildConfig
|
||||||
@ -163,6 +165,13 @@ class SettingsMenu : AbstractMenu() {
|
|||||||
return resultView
|
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")
|
@SuppressLint("SetTextI18n")
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
fun inject(viewModel: View, addView: (View) -> Unit) {
|
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 {
|
actions.filter { it.first.dependsOnProperty == null }.forEach {
|
||||||
addView(it.second())
|
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.ModContext
|
||||||
import me.rhunk.snapenhance.action.AbstractAction
|
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.CleanCache
|
||||||
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
|
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
|
||||||
import me.rhunk.snapenhance.action.impl.OpenMap
|
import me.rhunk.snapenhance.action.impl.OpenMap
|
||||||
@ -24,6 +25,7 @@ class ActionManager(
|
|||||||
load(ClearMessageLogger::class)
|
load(ClearMessageLogger::class)
|
||||||
load(RefreshMappings::class)
|
load(RefreshMappings::class)
|
||||||
load(OpenMap::class)
|
load(OpenMap::class)
|
||||||
|
load(CheckForUpdates::class)
|
||||||
|
|
||||||
actions.values.forEach(AbstractAction::init)
|
actions.values.forEach(AbstractAction::init)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import me.rhunk.snapenhance.Logger
|
|||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
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.ConfigEnumKeys
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
|
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
|
||||||
@ -75,7 +76,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(MeoPasscodeBypass::class)
|
register(MeoPasscodeBypass::class)
|
||||||
register(AppPasscode::class)
|
register(AppPasscode::class)
|
||||||
register(LocationSpoofer::class)
|
register(LocationSpoofer::class)
|
||||||
|
register(AutoUpdater::class)
|
||||||
|
|
||||||
initializeFeatures()
|
initializeFeatures()
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ class MappingManager(private val context: ModContext) : Manager {
|
|||||||
|
|
||||||
val loadingDialog = statusDialogBuilder.show()
|
val loadingDialog = statusDialogBuilder.show()
|
||||||
|
|
||||||
thread(start = true) {
|
context.executeAsync {
|
||||||
runCatching {
|
runCatching {
|
||||||
refresh()
|
refresh()
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user