commit 8d36ae8799648f83c73f21fb8c8363ba57294b25
parent 39769f8940c1b14f87c1473b5a7089599d2a24b3
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun,  4 Jun 2023 23:39:03 +0200

feat: auto updater (#23)

* feature: auto updater

* fix: formatting

* feat: auto updater

---------

Co-authored-by: auth <64337177+authorisation@users.noreply.github.com>
Diffstat:
Mapp/src/main/assets/lang/en_US.json | 23+++++++++++++++++++++--
Aapp/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt | 21+++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt | 12++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt | 14++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt | 14++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt | 3++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/config/ConfigCategory.kt | 1+
Mapp/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt | 11+++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt | 12+++++++++++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt | 2++
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt | 3++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt | 2+-
13 files changed, 222 insertions(+), 6 deletions(-)

diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json @@ -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..." } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/CheckForUpdates.kt @@ -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") + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt @@ -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() diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt @@ -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) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt @@ -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? { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigCategory.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigCategory.kt @@ -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"); } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt @@ -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( diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/AutoUpdater.kt @@ -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 + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt @@ -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"))) } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt @@ -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) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt @@ -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() } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt @@ -74,7 +74,7 @@ class MappingManager(private val context: ModContext) : Manager { val loadingDialog = statusDialogBuilder.show() - thread(start = true) { + context.executeAsync { runCatching { refresh() }.onSuccess {