commit e7ccaf312e0b7be206adcab703579ce1965eee4b
parent de1a676084eaf25f94a674e1c4db7444530d6744
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Fri, 26 May 2023 20:47:22 +0200
feat: media quality level override
Diffstat:
5 files changed, 95 insertions(+), 14 deletions(-)
diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json
@@ -12,7 +12,8 @@
 
     "action": {
         "clean_cache": "Clean Cache",
-        "clear_message_logger": "Clear Message Logger"
+        "clear_message_logger": "Clear Message Logger",
+        "refresh_mappings": "Refresh Mappings"
     },
    
     "property": {
@@ -41,11 +42,14 @@
         "auto_save": "Auto Save",
         "anti_auto_save": "Anti Auto Save Button",
         "snapchat_plus": "Snapchat Plus",
+        "disable_snap_splitting": "Disable Snap Splitting",
+        "disable_video_length_restriction": "Disable Video Length Restriction",
+        "override_media_quality": "Override Media Quality",
+        "media_quality_level": "Media Quality Level",
         "remove_voice_record_button": "Remove Voice Record Button",
         "remove_stickers_button": "Remove Stickers Button",
         "remove_cognac_button": "Remove Cognac Button",
         "remove_call_buttons": "Remove Call Buttons",
-        "disable_snap_splitting": "Disable Snap Splitting",
         "block_ads": "Block Ads",
         "streak_expiration_info": "Show Streak Expiration Info",
         "new_map_ui": "New Map UI",
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt
@@ -3,6 +3,7 @@ package me.rhunk.snapenhance.config
 import android.os.Environment
 import me.rhunk.snapenhance.config.impl.ConfigIntegerValue
 import me.rhunk.snapenhance.config.impl.ConfigStateListValue
+import me.rhunk.snapenhance.config.impl.ConfigStateSelection
 import me.rhunk.snapenhance.config.impl.ConfigStateValue
 import me.rhunk.snapenhance.config.impl.ConfigStringValue
 import java.io.File
@@ -143,6 +144,33 @@ enum class ConfigProperty(
     ANTI_AUTO_SAVE("property.anti_auto_save", "description.anti_auto_save", ConfigCategory.EXTRAS, ConfigStateValue(false)),
     SNAPCHAT_PLUS("property.snapchat_plus", "description.snapchat_plus", ConfigCategory.EXTRAS, ConfigStateValue(false)),
 
+    DISABLE_SNAP_SPLITTING(
+        "property.disable_snap_splitting",
+        "description.disable_snap_splitting",
+        ConfigCategory.TWEAKS,
+        ConfigStateValue(false)
+    ),
+    DISABLE_VIDEO_LENGTH_RESTRICTION(
+        "property.disable_video_length_restriction",
+        "description.disable_video_length_restriction",
+        ConfigCategory.TWEAKS,
+        ConfigStateValue(false)
+    ),
+    OVERRIDE_MEDIA_QUALITY(
+        "property.override_media_quality",
+        "description.override_media_quality",
+        ConfigCategory.TWEAKS,
+        ConfigStateValue(false)
+    ),
+    MEDIA_QUALITY_LEVEL(
+        "property.media_quality_level",
+        "description.media_quality_level",
+        ConfigCategory.TWEAKS,
+        ConfigStateSelection(
+            listOf("LEVEL_NONE", "LEVEL_1", "LEVEL_2", "LEVEL_3", "LEVEL_4", "LEVEL_5", "LEVEL_6", "LEVEL_7", "LEVEL_MAX"),
+            "LEVEL_NONE"
+        )
+    ),
     REMOVE_VOICE_RECORD_BUTTON(
         "property.remove_voice_record_button",
         "description.remove_voice_record_button",
@@ -167,18 +195,6 @@ enum class ConfigProperty(
         ConfigCategory.TWEAKS,
         ConfigStateValue(false)
     ),
-    DISABLE_SNAP_SPLITTING(
-        "property.disable_snap_splitting",
-        "description.disable_snap_splitting",
-        ConfigCategory.TWEAKS,
-        ConfigStateValue(false)
-    ),
-    DISABLE_VIDEO_LENGTH_RESTRICTION(
-        "property.disable_video_length_restriction",
-        "description.disable_video_length_restriction",
-        ConfigCategory.TWEAKS,
-        ConfigStateValue(false)
-    ),
     BLOCK_ADS("property.block_ads", "description.block_ads", ConfigCategory.TWEAKS, ConfigStateValue(false)),
     STREAK_EXPIRATION_INFO(
         "property.streak_expiration_info",
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/MediaQualityLevelOverride.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/MediaQualityLevelOverride.kt
@@ -0,0 +1,32 @@
+package me.rhunk.snapenhance.features.impl.extras
+
+import me.rhunk.snapenhance.Logger
+import me.rhunk.snapenhance.config.ConfigProperty
+import me.rhunk.snapenhance.features.Feature
+import me.rhunk.snapenhance.features.FeatureLoadParams
+import me.rhunk.snapenhance.hook.HookStage
+import me.rhunk.snapenhance.hook.Hooker
+
+class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParams = FeatureLoadParams.INIT_SYNC) {
+    override fun init() {
+        val enumQualityLevel = context.mappings.getMappedClass("enums", "QualityLevel")
+
+        Hooker.hookConstructor(context.androidContext.classLoader.loadClass("hfj"), HookStage.AFTER) {
+            Logger.log(it.thisObject())
+        }
+
+        Hooker.hook(context.mappings.getMappedClass("MediaQualityLevelProvider"),
+            context.mappings.getMappedValue("MediaQualityLevelProviderMethod"),
+            HookStage.BEFORE,
+            {context.config.bool(ConfigProperty.OVERRIDE_MEDIA_QUALITY)}
+        ) { param ->
+            val currentCompressionLevel = enumQualityLevel.enumConstants
+                .firstOrNull { it.toString() == context.config.state(ConfigProperty.MEDIA_QUALITY_LEVEL)} ?: run {
+                context.longToast("Invalid media quality level: ${context.config.state(ConfigProperty.MEDIA_QUALITY_LEVEL)}")
+                return@hook
+            }
+            Logger.debug("set media compression level to $currentCompressionLevel")
+            param.setResult(currentCompressionLevel)
+        }
+    }
+}+
\ No newline at end of file
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
@@ -10,6 +10,7 @@ import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
 import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
 import me.rhunk.snapenhance.features.impl.extras.AntiAutoSave
 import me.rhunk.snapenhance.features.impl.extras.AutoSave
+import me.rhunk.snapenhance.features.impl.extras.MediaQualityLevelOverride
 import me.rhunk.snapenhance.features.impl.extras.DisableVideoLengthRestriction
 import me.rhunk.snapenhance.features.impl.extras.ExternalMediaAsSnap
 import me.rhunk.snapenhance.features.impl.extras.Notifications
@@ -68,6 +69,7 @@ class FeatureManager(private val context: ModContext) : Manager {
         register(AntiAutoSave::class)
         register(UnlimitedSnapViewTime::class)
         register(DisableVideoLengthRestriction::class)
+        register(MediaQualityLevelOverride::class)
 
         initializeFeatures()
     }
@@ -80,6 +82,7 @@ class FeatureManager(private val context: ModContext) : Manager {
                     action(feature)
                 }.onFailure {
                     Logger.xposedLog("Failed to init feature ${feature.nameKey}", it)
+                    context.longToast("Failed to init feature ${feature.nameKey}")
                 }
             }
             if (!isAsync) {
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/EnumMapper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/EnumMapper.kt
@@ -1,8 +1,10 @@
 package me.rhunk.snapenhance.mapping.impl
 
+import me.rhunk.snapenhance.Logger
 import me.rhunk.snapenhance.Logger.debug
 import me.rhunk.snapenhance.mapping.Mapper
 import java.lang.reflect.Method
+import java.lang.reflect.Modifier
 import java.util.Objects
 
 
@@ -13,11 +15,22 @@ class EnumMapper : Mapper() {
         mappings: MutableMap<String, Any>
     ) {
         val enumMappings = HashMap<String, String>()
+        var enumQualityLevel: Class<*>? = null
+
         //settings classes have an interface that extends Serializable and contains the getName method
         //this enum classes are used to store the settings values
         //Setting enum class -> implements an interface -> getName method
         classes.forEach { clazz ->
             if (!clazz.isEnum) return@forEach
+
+            //quality level enum
+            if (enumQualityLevel == null) {
+                if (clazz.enumConstants.any { it.toString().startsWith("LEVEL_NONE") }) {
+                    enumMappings["QualityLevel"] = clazz.name
+                    enumQualityLevel = clazz
+                }
+            }
+
             if (clazz.interfaces.isEmpty()) return@forEach
             val serializableInterfaceClass = clazz.interfaces[0]
             if (serializableInterfaceClass.methods
@@ -35,6 +48,18 @@ class EnumMapper : Mapper() {
                 }
             }
         }
+
+        //find the media quality level provider
+        for (clazz in classes) {
+            if (!Modifier.isAbstract(clazz.modifiers)) continue
+            if (clazz.fields.none { Modifier.isTransient(it.modifiers) }) continue
+            clazz.methods.firstOrNull { it.returnType == enumQualityLevel }?.let {
+                mappings["MediaQualityLevelProvider"] = clazz.name
+                mappings["MediaQualityLevelProviderMethod"] = it.name
+                Logger.debug("found MediaQualityLevelProvider: ${clazz.name}.${it.name}")
+            }
+        }
+
         debug("found " + enumMappings.size + " enums")
         mappings["enums"] = enumMappings
     }