From 2c9530686973def2dae9187acd900169f4895dc9 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:41:34 +0200 Subject: [PATCH 1/5] update CodecMod targetSdk to 37 --- CodecMod/build.gradle.kts | 2 +- .../programminghoch10/CodecMod/MediaCodecInfoWrapper.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CodecMod/build.gradle.kts b/CodecMod/build.gradle.kts index 2f78ef6..0fff63f 100644 --- a/CodecMod/build.gradle.kts +++ b/CodecMod/build.gradle.kts @@ -7,7 +7,7 @@ android { defaultConfig { minSdk = 16 - targetSdk = 35 + targetSdk = 37 multiDexEnabled = true } compileOptions { diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java index 29c340b..fb2edcf 100644 --- a/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java +++ b/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java @@ -40,17 +40,17 @@ public String getName() { return mediaCodecInfo.getName(); } - @RequiresApi(api = Build.VERSION_CODES.Q) + @RequiresApi(Build.VERSION_CODES.Q) public boolean isHardwareAccelerated() { return mediaCodecInfo.isHardwareAccelerated(); } - @RequiresApi(api = Build.VERSION_CODES.Q) + @RequiresApi(Build.VERSION_CODES.Q) public boolean isSoftwareOnly() { return mediaCodecInfo.isSoftwareOnly(); } - @RequiresApi(api = Build.VERSION_CODES.Q) + @RequiresApi(Build.VERSION_CODES.Q) public boolean isVendor() { return mediaCodecInfo.isVendor(); } From 32dc82915773eb83120efd691ef2cf4e497c23a4 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:59:19 +0200 Subject: [PATCH 2/5] migrate CodecMod to kotlin --- CodecMod/build.gradle.kts | 3 +- .../CodecMod/CodecStore.java | 80 ------------- .../com/programminghoch10/CodecMod/Hook.java | 105 ---------------- .../CodecMod/MediaCodecInfoWrapper.java | 69 ----------- .../CodecMod/SettingsActivity.java | 86 ------------- .../programminghoch10/CodecMod/CodecStore.kt | 79 ++++++++++++ .../com/programminghoch10/CodecMod/Hook.kt | 113 ++++++++++++++++++ .../CodecMod/MediaCodecInfoWrapper.kt | 45 +++++++ .../CodecMod/SettingsActivity.kt | 95 +++++++++++++++ 9 files changed, 334 insertions(+), 341 deletions(-) delete mode 100644 CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java delete mode 100644 CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java delete mode 100644 CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java delete mode 100644 CodecMod/src/main/java/com/programminghoch10/CodecMod/SettingsActivity.java create mode 100644 CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt create mode 100644 CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/Hook.kt create mode 100644 CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt create mode 100644 CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt diff --git a/CodecMod/build.gradle.kts b/CodecMod/build.gradle.kts index 0fff63f..5c6891a 100644 --- a/CodecMod/build.gradle.kts +++ b/CodecMod/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.buildlogic.android.application) + alias(libs.plugins.buildlogic.kotlin.android) } android { @@ -16,6 +17,6 @@ android { } dependencies { - implementation(libs.androidx.preference) + implementation(libs.androidx.preference.ktx) coreLibraryDesugaring(libs.android.desugarJdkLibs) } diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java deleted file mode 100644 index cc821b8..0000000 --- a/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.programminghoch10.CodecMod; - -import static android.content.Context.MODE_WORLD_READABLE; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Build; - -import java.util.LinkedList; -import java.util.List; - -import de.robv.android.xposed.XSharedPreferences; - -public class CodecStore { - static final boolean DEFAULT_VALUE = true; - private static final boolean REMOVE_DEFAULT_VALUE_FROM_CONFIG = true; - private static final String PREFERENCES = "codecs"; - private final SharedPreferences sharedPreferences; - private final List receivers = new LinkedList<>(); - - CodecStore(Context context) { - this.sharedPreferences = context.getSharedPreferences(PREFERENCES, MODE_WORLD_READABLE); - } - - CodecStore() { - this.sharedPreferences = new XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES); - } - - static String getKey(MediaCodecInfoWrapper mediaCodecInfo) { - return "codec_" + mediaCodecInfo.getCanonicalName(); - } - - boolean getCodecPreference(MediaCodecInfoWrapper mediaCodecInfo) { - return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE); - } - - boolean setCodecPreference(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) { - boolean success; - if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) { - success = sharedPreferences.edit().remove(getKey(mediaCodecInfo)).commit(); - } else { - success = sharedPreferences.edit().putBoolean(getKey(mediaCodecInfo), enabled).commit(); - } - if (!success) return false; - dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled); - return true; - } - - void registerOnCodecPreferenceChangedListener( - MediaCodecInfoWrapper mediaCodecInfo, - OnCodecPreferenceChangedListener onCodecPreferenceChangedListener - ) { - OnCodecPreferenceChangedListenerMeta listener = new OnCodecPreferenceChangedListenerMeta(); - listener.mediaCodecInfo = mediaCodecInfo; - listener.callback = onCodecPreferenceChangedListener; - receivers.add(listener); - } - - private void dispatchOnCodecPreferenceChanged(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - receivers.stream() - .filter(r -> getKey(r.mediaCodecInfo).equals(getKey(mediaCodecInfo))) - .forEach(r -> r.callback.onCodecPreferenceChanged(enabled)); - } else { - for (OnCodecPreferenceChangedListenerMeta receiver : receivers) { - if (getKey(receiver.mediaCodecInfo).equals(getKey(mediaCodecInfo))) receiver.callback.onCodecPreferenceChanged(enabled); - } - } - } - - interface OnCodecPreferenceChangedListener { - void onCodecPreferenceChanged(boolean value); - } - - private static class OnCodecPreferenceChangedListenerMeta { - MediaCodecInfoWrapper mediaCodecInfo; - OnCodecPreferenceChangedListener callback; - } -} diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java deleted file mode 100644 index 556a509..0000000 --- a/CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.programminghoch10.CodecMod; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; -import android.os.Build; - -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -import de.robv.android.xposed.IXposedHookLoadPackage; -import de.robv.android.xposed.XC_MethodReplacement; -import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; -import de.robv.android.xposed.callbacks.XC_LoadPackage; - -public class Hook implements IXposedHookLoadPackage { - - MediaCodecInfo[] getFilteredMediaCodecInfos(MediaCodecInfo[] unfilteredMediaCodecInfos) { - CodecStore codecStore = new CodecStore(); - return Arrays.stream(unfilteredMediaCodecInfos) - .map(MediaCodecInfoWrapper::new) - .filter(codecStore::getCodecPreference) - .map(MediaCodecInfoWrapper::getOriginalMediaCodecInfo) - .toArray(MediaCodecInfo[]::new); - } - - // helper function, only to be used on mediaCodecs = new LinkedList<>(); - final int codecCount = - (int) XposedBridge.invokeOriginalMethod(XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecCount"), null, null); - for (int i = 0; i < codecCount; i++) - mediaCodecs.add((MediaCodecInfo) XposedBridge.invokeOriginalMethod( - XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecInfoAt"), - null, - new Object[]{ i } - )); - return getFilteredMediaCodecInfos(mediaCodecs.toArray(MediaCodecInfo[]::new)); - } - - @Override - public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { - if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) return; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - XposedHelpers.findAndHookMethod( - MediaCodecList.class, "getCodecInfos", new XC_MethodReplacement() { - @Override - protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { - MediaCodecInfo[] mediaCodecInfos = - (MediaCodecInfo[]) XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args); - if (mediaCodecInfos.length == 0) return mediaCodecInfos; - return getFilteredMediaCodecInfos(mediaCodecInfos); - } - } - ); - - // reimplementations of deprecated methods for compatibility - XposedHelpers.findAndHookMethod( - MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() { - @Override - protected Object replaceHookedMethod(MethodHookParam param) { - return (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos().length; - } - } - ); - XposedHelpers.findAndHookMethod( - MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() { - @Override - protected Object replaceHookedMethod(MethodHookParam param) { - final int position = (int) param.args[0]; - MediaCodecInfo[] mediaCodecInfos = (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos(); - if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException(); - return mediaCodecInfos[position]; - } - } - ); - } else { - XposedHelpers.findAndHookMethod( - MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() { - @Override - protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { - return getFilteredMediaCodecInfos().length; - } - } - ); - XposedHelpers.findAndHookMethod( - MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() { - @Override - protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { - final int position = (int) param.args[0]; - MediaCodecInfo[] mediaCodecInfos = getFilteredMediaCodecInfos(); - if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException(); - return mediaCodecInfos[position]; - } - } - ); - } - } -} diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java deleted file mode 100644 index fb2edcf..0000000 --- a/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.programminghoch10.CodecMod; - -import android.media.MediaCodecInfo; -import android.os.Build; - -import androidx.annotation.RequiresApi; - -/** - * drop in replacement for MediaCodecInfo - * with compatibility checks for older SDKs - * - * @see MediaCodecInfo - */ -public class MediaCodecInfoWrapper { - private final MediaCodecInfo mediaCodecInfo; - - MediaCodecInfoWrapper(MediaCodecInfo mediaCodecInfo) { - this.mediaCodecInfo = mediaCodecInfo; - } - - public MediaCodecInfo getOriginalMediaCodecInfo() { - return mediaCodecInfo; - } - - public String getCanonicalName() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return mediaCodecInfo.getCanonicalName(); - } - return mediaCodecInfo.getName(); - } - - public boolean isAlias() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return mediaCodecInfo.isAlias(); - } - return false; - } - - public String getName() { - return mediaCodecInfo.getName(); - } - - @RequiresApi(Build.VERSION_CODES.Q) - public boolean isHardwareAccelerated() { - return mediaCodecInfo.isHardwareAccelerated(); - } - - @RequiresApi(Build.VERSION_CODES.Q) - public boolean isSoftwareOnly() { - return mediaCodecInfo.isSoftwareOnly(); - } - - @RequiresApi(Build.VERSION_CODES.Q) - public boolean isVendor() { - return mediaCodecInfo.isVendor(); - } - - public boolean isEncoder() { - return mediaCodecInfo.isEncoder(); - } - - public boolean isDecoder() { - return !isEncoder(); - } - - public String[] getSupportedTypes() { - return mediaCodecInfo.getSupportedTypes(); - } -} diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/SettingsActivity.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/SettingsActivity.java deleted file mode 100644 index 92ee7e7..0000000 --- a/CodecMod/src/main/java/com/programminghoch10/CodecMod/SettingsActivity.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.programminghoch10.CodecMod; - -import android.app.ActionBar; -import android.media.MediaCodecList; -import android.os.Build; -import android.os.Bundle; - -import androidx.fragment.app.FragmentActivity; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -public class SettingsActivity extends FragmentActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.settings_activity); - if (savedInstanceState == null) { - getSupportFragmentManager().beginTransaction().replace(R.id.settings, new SettingsFragment()).commit(); - } - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(getSupportFragmentManager().getBackStackEntryCount() > 0); - } - } - - public static class SettingsFragment extends PreferenceFragmentCompat { - private static final boolean SHOW_ALIASES = true; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.root_preferences, rootKey); - //getPreferenceManager().setSharedPreferencesName("codecs"); - CodecStore codecStore = new CodecStore(requireContext()); - PreferenceCategory decodersPreferenceCategory = findPreference("category_decoders"); - PreferenceCategory encodersPreferenceCategory = findPreference("category_encoders"); - - List mediaCodecs; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS); - mediaCodecs = Arrays.stream(mediaCodecList.getCodecInfos()).map(MediaCodecInfoWrapper::new).toList(); - } else { - mediaCodecs = new LinkedList<>(); - for (int i = 0; i < MediaCodecList.getCodecCount(); i++) - mediaCodecs.add(new MediaCodecInfoWrapper(MediaCodecList.getCodecInfoAt(i))); - } - for (MediaCodecInfoWrapper mediaCodecInfo : mediaCodecs) { - if (mediaCodecInfo.isAlias() && !SHOW_ALIASES) continue; - SwitchPreference preference = new SwitchPreference(requireContext()); - preference.setPersistent(false); - preference.setDefaultValue(CodecStore.DEFAULT_VALUE); - preference.setKey(CodecStore.getKey(mediaCodecInfo)); - preference.setOnPreferenceChangeListener((p, n) -> codecStore.setCodecPreference(mediaCodecInfo, (Boolean) n)); - codecStore.registerOnCodecPreferenceChangedListener( - mediaCodecInfo, value -> { - if (preference.isChecked() != value) preference.setChecked(value); - } - ); - preference.setTitle(mediaCodecInfo.getName() // - + (mediaCodecInfo.getName().equals(mediaCodecInfo.getCanonicalName()) ? "" : " (" + mediaCodecInfo.getCanonicalName() + ")")); - StringBuilder summaryBuilder = new StringBuilder(); - summaryBuilder.append(String.format(getString(R.string.supported_types), Arrays.toString(mediaCodecInfo.getSupportedTypes()))); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - summaryBuilder.append("\n"); - summaryBuilder.append(String.format(getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated())); - summaryBuilder.append("\n"); - summaryBuilder.append(String.format(getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly())); - if (SHOW_ALIASES) { - summaryBuilder.append("\n"); - summaryBuilder.append(String.format(getString(R.string.alias), mediaCodecInfo.isAlias())); - } - summaryBuilder.append("\n"); - summaryBuilder.append(String.format(getString(R.string.vendor), mediaCodecInfo.isVendor())); - } - preference.setSummary(summaryBuilder); - PreferenceCategory preferenceCategory = mediaCodecInfo.isEncoder() ? encodersPreferenceCategory : decodersPreferenceCategory; - preferenceCategory.addPreference(preference); - preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo)); - } - } - } -} diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt new file mode 100644 index 0000000..a18e267 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt @@ -0,0 +1,79 @@ +package com.programminghoch10.CodecMod + +import java.util.* +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import de.robv.android.xposed.XSharedPreferences + +class CodecStore { + private val sharedPreferences: SharedPreferences + private val receivers: MutableList = LinkedList() + + internal constructor(context: Context) { + this.sharedPreferences = context.getSharedPreferences(PREFERENCES, Context.MODE_WORLD_READABLE) + } + + internal constructor() { + this.sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES) + } + + fun getCodecPreference(mediaCodecInfo: MediaCodecInfoWrapper): Boolean { + return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE) + } + + fun setCodecPreference(mediaCodecInfo: MediaCodecInfoWrapper, enabled: Boolean): Boolean { + val success = sharedPreferences.edit().apply { + if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) { + remove(getKey(mediaCodecInfo)) + } else { + putBoolean(getKey(mediaCodecInfo), enabled) + } + }.commit() + if (!success) return false + dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled) + return true + } + + fun registerOnCodecPreferenceChangedListener( + mediaCodecInfo: MediaCodecInfoWrapper, + onCodecPreferenceChangedListener: OnCodecPreferenceChangedListener, + ) { + val listener = OnCodecPreferenceChangedListenerMeta() + listener.mediaCodecInfo = mediaCodecInfo + listener.callback = onCodecPreferenceChangedListener + receivers.add(listener) + } + + private fun dispatchOnCodecPreferenceChanged(mediaCodecInfo: MediaCodecInfoWrapper, enabled: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + receivers.stream().filter { r: OnCodecPreferenceChangedListenerMeta? -> + getKey(r!!.mediaCodecInfo!!) == getKey(mediaCodecInfo) + }.forEach { r: OnCodecPreferenceChangedListenerMeta? -> r!!.callback!!.onCodecPreferenceChanged(enabled) } + } else { + for (receiver in receivers) { + if (getKey(receiver.mediaCodecInfo!!) == getKey(mediaCodecInfo)) receiver.callback!!.onCodecPreferenceChanged( + enabled + ) + } + } + } + + fun interface OnCodecPreferenceChangedListener { + fun onCodecPreferenceChanged(value: Boolean) + } + + private class OnCodecPreferenceChangedListenerMeta { + var mediaCodecInfo: MediaCodecInfoWrapper? = null + var callback: OnCodecPreferenceChangedListener? = null + } + + companion object { + const val DEFAULT_VALUE: Boolean = true + private const val REMOVE_DEFAULT_VALUE_FROM_CONFIG = true + private const val PREFERENCES = "codecs" + fun getKey(mediaCodecInfo: MediaCodecInfoWrapper): String { + return "codec_" + mediaCodecInfo.canonicalName + } + } +} diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/Hook.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/Hook.kt new file mode 100644 index 0000000..9113996 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/Hook.kt @@ -0,0 +1,113 @@ +package com.programminghoch10.CodecMod + +import java.lang.reflect.InvocationTargetException +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.media.MediaCodecInfo +import android.media.MediaCodecList +import android.os.Build +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XC_MethodReplacement +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam + +class Hook : IXposedHookLoadPackage { + fun getFilteredMediaCodecInfos(unfilteredMediaCodecInfos: Array): Array { + val codecStore = CodecStore() + return unfilteredMediaCodecInfos.map { mediaCodecInfo -> MediaCodecInfoWrapper(mediaCodecInfo) } + .filter { mediaCodecInfo -> codecStore.getCodecPreference(mediaCodecInfo) } + .map { it.originalMediaCodecInfo } + .toTypedArray() + } + + @get:Throws(InvocationTargetException::class, IllegalAccessException::class) + @get:TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @get:SuppressLint("UseRequiresApi") + val filteredMediaCodecInfos: Array + // helper function, only to be used on = mutableListOf() + val codecCount = XposedBridge.invokeOriginalMethod( + XposedHelpers.findMethodExact( + MediaCodecList::class.java, + "getCodecCount", + emptyArray(), + ), + null, + null, + ) as Int + for (i in 0..(i), + ) as MediaCodecInfo + ) + return getFilteredMediaCodecInfos(mediaCodecs.toTypedArray()) + } + + override fun handleLoadPackage(lpparam: LoadPackageParam) { + if (lpparam.packageName == BuildConfig.APPLICATION_ID) return + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + XposedHelpers.findAndHookMethod( + MediaCodecList::class.java, "getCodecInfos", object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Array { + val mediaCodecInfos = XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args) as Array + if (mediaCodecInfos.isEmpty()) return mediaCodecInfos + return getFilteredMediaCodecInfos(mediaCodecInfos) + } + }) + + // re-implementations of deprecated methods for compatibility + XposedHelpers.findAndHookMethod( + MediaCodecList::class.java, + "getCodecCount", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Int { + return MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.size + } + }, + ) + XposedHelpers.findAndHookMethod( + MediaCodecList::class.java, + "getCodecInfoAt", + Int::class.javaPrimitiveType, + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): MediaCodecInfo { + val position = param.args[0] as Int + val mediaCodecInfos = MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos + require(position in mediaCodecInfos.indices) + return mediaCodecInfos[position] + } + }, + ) + } else { + XposedHelpers.findAndHookMethod( + MediaCodecList::class.java, + "getCodecCount", + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Int { + return filteredMediaCodecInfos.size + } + }, + ) + XposedHelpers.findAndHookMethod( + MediaCodecList::class.java, + "getCodecInfoAt", + Int::class.javaPrimitiveType, + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): MediaCodecInfo { + val position = param.args[0] as Int + require(!(position < 0 || position >= filteredMediaCodecInfos.size)) + return filteredMediaCodecInfos[position] + } + }, + ) + } + } +} diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt new file mode 100644 index 0000000..0084908 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt @@ -0,0 +1,45 @@ +package com.programminghoch10.CodecMod + +import android.media.MediaCodecInfo +import android.os.Build +import androidx.annotation.RequiresApi + +/** + * drop in replacement for MediaCodecInfo + * with compatibility checks for older SDKs + * + * @see MediaCodecInfo + */ +class MediaCodecInfoWrapper internal constructor(val originalMediaCodecInfo: MediaCodecInfo) { + val canonicalName: String + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) originalMediaCodecInfo.canonicalName + else originalMediaCodecInfo.name + + val isAlias: Boolean + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) originalMediaCodecInfo.isAlias + else false + + val name: String + get() = originalMediaCodecInfo.name + + @get:RequiresApi(Build.VERSION_CODES.Q) + val isHardwareAccelerated: Boolean + get() = originalMediaCodecInfo.isHardwareAccelerated + + @get:RequiresApi(Build.VERSION_CODES.Q) + val isSoftwareOnly: Boolean + get() = originalMediaCodecInfo.isSoftwareOnly + + @get:RequiresApi(Build.VERSION_CODES.Q) + val isVendor: Boolean + get() = originalMediaCodecInfo.isVendor + + val isEncoder: Boolean + get() = originalMediaCodecInfo.isEncoder + + val isDecoder: Boolean + get() = !this.isEncoder + + val supportedTypes: Array? + get() = originalMediaCodecInfo.getSupportedTypes() +} diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt new file mode 100644 index 0000000..080dd6b --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt @@ -0,0 +1,95 @@ +package com.programminghoch10.CodecMod + +import android.media.MediaCodecList +import android.os.Build +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference + +class SettingsActivity : FragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.settings_activity) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit() + } + actionBar?.setDisplayHomeAsUpEnabled(supportFragmentManager.backStackEntryCount > 0) + } + + class SettingsFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.root_preferences, rootKey) + //getPreferenceManager().setSharedPreferencesName("codecs"); + val codecStore = CodecStore(requireContext()) + val decodersPreferenceCategory = findPreference("category_decoders")!! + val encodersPreferenceCategory = findPreference("category_encoders")!! + + var mediaCodecs: List + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS) + mediaCodecs = mediaCodecList.codecInfos.map { mediaCodecInfo -> MediaCodecInfoWrapper(mediaCodecInfo) } + } else { + mediaCodecs = mutableListOf() + for (i in 0.. + codecStore.setCodecPreference(mediaCodecInfo, (newValue as Boolean)) + } + codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfo) { value: Boolean -> + if (preference.isChecked != value) preference.setChecked(value) + } + preference.title = + mediaCodecInfo.name + (if (mediaCodecInfo.name == mediaCodecInfo.canonicalName) "" else " (" + mediaCodecInfo.canonicalName + ")") + val summaryBuilder = StringBuilder() + summaryBuilder.append( + String.format( + getString(R.string.supported_types), mediaCodecInfo.supportedTypes.contentToString() + ) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + summaryBuilder.append("\n") + summaryBuilder.append( + String.format( + getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated + ) + ) + summaryBuilder.append("\n") + summaryBuilder.append( + String.format( + getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly + ) + ) + if (SHOW_ALIASES) { + summaryBuilder.append("\n") + summaryBuilder.append(String.format(getString(R.string.alias), mediaCodecInfo.isAlias)) + } + summaryBuilder.append("\n") + summaryBuilder.append(String.format(getString(R.string.vendor), mediaCodecInfo.isVendor)) + } + preference.setSummary(summaryBuilder) + val preferenceCategory = if (mediaCodecInfo.isEncoder) encodersPreferenceCategory else decodersPreferenceCategory + preferenceCategory.addPreference(preference) + preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo)) + } + } + + companion object { + private const val SHOW_ALIASES = true + } + } +} From b713a3a6d31116d6917520e8c11e69672fbd6398 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:06:33 +0200 Subject: [PATCH 3/5] CodecMod: refactor preference into subclass --- .../CodecMod/MediaCodecInfoWrapper.kt | 4 ++ .../CodecMod/MediaCodecPreference.kt | 63 +++++++++++++++++++ .../CodecMod/SettingsActivity.kt | 45 +------------ 3 files changed, 69 insertions(+), 43 deletions(-) create mode 100644 CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt index 0084908..4996780 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt @@ -1,5 +1,7 @@ package com.programminghoch10.CodecMod +import android.annotation.SuppressLint +import android.annotation.TargetApi import android.media.MediaCodecInfo import android.os.Build import androidx.annotation.RequiresApi @@ -15,6 +17,8 @@ class MediaCodecInfoWrapper internal constructor(val originalMediaCodecInfo: Med get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) originalMediaCodecInfo.canonicalName else originalMediaCodecInfo.name + @get:SuppressLint("UseRequiresApi") + @get:TargetApi(Build.VERSION_CODES.Q) val isAlias: Boolean get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) originalMediaCodecInfo.isAlias else false diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt new file mode 100644 index 0000000..c02545d --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt @@ -0,0 +1,63 @@ +package com.programminghoch10.CodecMod + +import java.lang.String +import kotlin.Boolean +import kotlin.CharSequence +import kotlin.text.StringBuilder +import kotlin.toString +import android.content.Context +import android.media.MediaCodecInfo +import android.os.Build +import androidx.preference.SwitchPreference +import com.programminghoch10.CodecMod.SettingsActivity.SettingsFragment.Companion.SHOW_ALIASES + +class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCodecInfoWrapper: MediaCodecInfoWrapper) : SwitchPreference(context) { + + constructor(context: Context, codecStore: CodecStore, mediaCodecInfo: MediaCodecInfo) : this( + context, codecStore, MediaCodecInfoWrapper(mediaCodecInfo) + ) + + init { + isPersistent = false + setDefaultValue(CodecStore.DEFAULT_VALUE) + key = CodecStore.getKey(mediaCodecInfoWrapper) + + setOnPreferenceChangeListener { _, newValue -> + codecStore.setCodecPreference( + mediaCodecInfoWrapper, newValue as Boolean + ) + } + + codecStore.registerOnCodecPreferenceChangedListener( + mediaCodecInfoWrapper, { value -> + if (isChecked != value) setChecked(value) + }) + + title = mediaCodecInfoWrapper.name + if (mediaCodecInfoWrapper.name != mediaCodecInfoWrapper.canonicalName) title = title.toString() + "(${mediaCodecInfoWrapper.canonicalName})" + + isChecked = codecStore.getCodecPreference(mediaCodecInfoWrapper) + } + + override fun getSummary(): CharSequence { + val summaryBuilder = StringBuilder() + summaryBuilder.append( + String.format(context.getString(R.string.supported_types), mediaCodecInfoWrapper.supportedTypes.contentToString()) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + summaryBuilder.append("\n") + summaryBuilder.append( + String.format(context.getString(R.string.hardware_accelerated), mediaCodecInfoWrapper.isHardwareAccelerated) + ) + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.software_only), mediaCodecInfoWrapper.isSoftwareOnly)) + if (SHOW_ALIASES) { + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.alias), mediaCodecInfoWrapper.isAlias)) + } + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.vendor), mediaCodecInfoWrapper.isVendor)) + } + return summaryBuilder + } +} diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt index 080dd6b..67a8eb5 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt @@ -4,10 +4,8 @@ import android.media.MediaCodecList import android.os.Build import android.os.Bundle import androidx.fragment.app.FragmentActivity -import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreference class SettingsActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -43,53 +41,14 @@ class SettingsActivity : FragmentActivity() { } for (mediaCodecInfo in mediaCodecs) { if (mediaCodecInfo.isAlias && !SHOW_ALIASES) continue - val preference = SwitchPreference(requireContext()) - preference.isPersistent = false - preference.setDefaultValue(CodecStore.DEFAULT_VALUE) - preference.setKey(CodecStore.getKey(mediaCodecInfo)) - preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - codecStore.setCodecPreference(mediaCodecInfo, (newValue as Boolean)) - } - codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfo) { value: Boolean -> - if (preference.isChecked != value) preference.setChecked(value) - } - preference.title = - mediaCodecInfo.name + (if (mediaCodecInfo.name == mediaCodecInfo.canonicalName) "" else " (" + mediaCodecInfo.canonicalName + ")") - val summaryBuilder = StringBuilder() - summaryBuilder.append( - String.format( - getString(R.string.supported_types), mediaCodecInfo.supportedTypes.contentToString() - ) - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - summaryBuilder.append("\n") - summaryBuilder.append( - String.format( - getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated - ) - ) - summaryBuilder.append("\n") - summaryBuilder.append( - String.format( - getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly - ) - ) - if (SHOW_ALIASES) { - summaryBuilder.append("\n") - summaryBuilder.append(String.format(getString(R.string.alias), mediaCodecInfo.isAlias)) - } - summaryBuilder.append("\n") - summaryBuilder.append(String.format(getString(R.string.vendor), mediaCodecInfo.isVendor)) - } - preference.setSummary(summaryBuilder) + val preference = MediaCodecPreference(requireContext(), codecStore, mediaCodecInfo) val preferenceCategory = if (mediaCodecInfo.isEncoder) encodersPreferenceCategory else decodersPreferenceCategory preferenceCategory.addPreference(preference) - preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo)) } } companion object { - private const val SHOW_ALIASES = true + const val SHOW_ALIASES = true } } } From 5575ed837b861af9eb97b92a0df2a8185b312076 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:06:23 +0200 Subject: [PATCH 4/5] add filters to CodecMod SettingsActivity --- .../programminghoch10/CodecMod/CodecStore.kt | 67 ++++++++---------- .../CodecMod/MediaCodecInfoWrapper.kt | 5 +- .../CodecMod/MediaCodecPreference.kt | 70 +++++++++++-------- .../CodecMod/SettingsActivity.kt | 69 ++++++++++++++++-- .../src/main/res/menu/settings_options.xml | 56 +++++++++++++++ CodecMod/src/main/res/values/strings.xml | 14 +++- .../src/main/res/xml/root_preferences.xml | 16 ++++- 7 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 CodecMod/src/main/res/menu/settings_options.xml diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt index a18e267..c9cf659 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt @@ -1,72 +1,65 @@ package com.programminghoch10.CodecMod -import java.util.* import android.content.Context import android.content.SharedPreferences -import android.os.Build +import androidx.core.content.edit +import androidx.preference.PreferenceDataStore import de.robv.android.xposed.XSharedPreferences -class CodecStore { +class CodecStore : PreferenceDataStore { private val sharedPreferences: SharedPreferences - private val receivers: MutableList = LinkedList() + private val receivers: MutableList = mutableListOf() - internal constructor(context: Context) { - this.sharedPreferences = context.getSharedPreferences(PREFERENCES, Context.MODE_WORLD_READABLE) + constructor(context: Context) { + sharedPreferences = context.getSharedPreferences(PREFERENCES, Context.MODE_WORLD_READABLE) } - internal constructor() { - this.sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES) + constructor() { + sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES) } fun getCodecPreference(mediaCodecInfo: MediaCodecInfoWrapper): Boolean { - return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE) + return getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE) } - fun setCodecPreference(mediaCodecInfo: MediaCodecInfoWrapper, enabled: Boolean): Boolean { - val success = sharedPreferences.edit().apply { - if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) { - remove(getKey(mediaCodecInfo)) + override fun getBoolean(key: String, defValue: Boolean): Boolean { + return sharedPreferences.getBoolean(key, defValue) + } + + override fun putBoolean(key: String, value: Boolean) { + sharedPreferences.edit(commit = true) { + if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && value == DEFAULT_VALUE) { + remove(key) } else { - putBoolean(getKey(mediaCodecInfo), enabled) + putBoolean(key, value) } - }.commit() - if (!success) return false - dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled) - return true + } + dispatchOnCodecPreferenceChanged(key, value) } fun registerOnCodecPreferenceChangedListener( mediaCodecInfo: MediaCodecInfoWrapper, onCodecPreferenceChangedListener: OnCodecPreferenceChangedListener, ) { - val listener = OnCodecPreferenceChangedListenerMeta() - listener.mediaCodecInfo = mediaCodecInfo - listener.callback = onCodecPreferenceChangedListener + val listener = OnCodecPreferenceChangedListenerMeta( + mediaCodecInfo, + onCodecPreferenceChangedListener, + ) receivers.add(listener) } - private fun dispatchOnCodecPreferenceChanged(mediaCodecInfo: MediaCodecInfoWrapper, enabled: Boolean) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - receivers.stream().filter { r: OnCodecPreferenceChangedListenerMeta? -> - getKey(r!!.mediaCodecInfo!!) == getKey(mediaCodecInfo) - }.forEach { r: OnCodecPreferenceChangedListenerMeta? -> r!!.callback!!.onCodecPreferenceChanged(enabled) } - } else { - for (receiver in receivers) { - if (getKey(receiver.mediaCodecInfo!!) == getKey(mediaCodecInfo)) receiver.callback!!.onCodecPreferenceChanged( - enabled - ) - } - } + private fun dispatchOnCodecPreferenceChanged(key: String, enabled: Boolean) { + receivers.filter { getKey(it.mediaCodecInfo) == key }.forEach { it.callback.onCodecPreferenceChanged(enabled) } } fun interface OnCodecPreferenceChangedListener { fun onCodecPreferenceChanged(value: Boolean) } - private class OnCodecPreferenceChangedListenerMeta { - var mediaCodecInfo: MediaCodecInfoWrapper? = null - var callback: OnCodecPreferenceChangedListener? = null - } + private data class OnCodecPreferenceChangedListenerMeta( + val mediaCodecInfo: MediaCodecInfoWrapper, + val callback: OnCodecPreferenceChangedListener, + ) companion object { const val DEFAULT_VALUE: Boolean = true diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt index 4996780..cf7a046 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt @@ -44,6 +44,9 @@ class MediaCodecInfoWrapper internal constructor(val originalMediaCodecInfo: Med val isDecoder: Boolean get() = !this.isEncoder - val supportedTypes: Array? + val supportedTypes: Array get() = originalMediaCodecInfo.getSupportedTypes() + + val isVideoCodec get() = supportedTypes.any { it.startsWith("video") } + val isAudioCodec get() = supportedTypes.any { it.startsWith("audio") } } diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt index c02545d..df98f97 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt @@ -1,15 +1,10 @@ package com.programminghoch10.CodecMod -import java.lang.String -import kotlin.Boolean -import kotlin.CharSequence -import kotlin.text.StringBuilder -import kotlin.toString import android.content.Context import android.media.MediaCodecInfo import android.os.Build +import androidx.annotation.RequiresApi import androidx.preference.SwitchPreference -import com.programminghoch10.CodecMod.SettingsActivity.SettingsFragment.Companion.SHOW_ALIASES class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCodecInfoWrapper: MediaCodecInfoWrapper) : SwitchPreference(context) { @@ -17,41 +12,48 @@ class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCo context, codecStore, MediaCodecInfoWrapper(mediaCodecInfo) ) + private lateinit var filterSpec: FilterSpec + fun setFilterSpec(filterSpec: FilterSpec) { + this.filterSpec = filterSpec + isVisible = when { + !filterSpec.showAliases && mediaCodecInfoWrapper.isAlias -> false + !filterSpec.showVideoCodecs && mediaCodecInfoWrapper.isVideoCodec -> false + !filterSpec.showAudioCodecs && mediaCodecInfoWrapper.isAudioCodec -> false + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !filterSpec.showHardwareCodecs && mediaCodecInfoWrapper.isHardwareAccelerated -> false + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !filterSpec.showSoftwareCodecs && mediaCodecInfoWrapper.isSoftwareOnly -> false + else -> true + } + notifyChanged() // to update summary + } + init { - isPersistent = false + preferenceDataStore = codecStore setDefaultValue(CodecStore.DEFAULT_VALUE) key = CodecStore.getKey(mediaCodecInfoWrapper) - - setOnPreferenceChangeListener { _, newValue -> - codecStore.setCodecPreference( - mediaCodecInfoWrapper, newValue as Boolean - ) - } - - codecStore.registerOnCodecPreferenceChangedListener( - mediaCodecInfoWrapper, { value -> - if (isChecked != value) setChecked(value) - }) - - title = mediaCodecInfoWrapper.name - if (mediaCodecInfoWrapper.name != mediaCodecInfoWrapper.canonicalName) title = title.toString() + "(${mediaCodecInfoWrapper.canonicalName})" - - isChecked = codecStore.getCodecPreference(mediaCodecInfoWrapper) + codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfoWrapper) { value -> isChecked = value } + } + + override fun getTitle(): CharSequence { + val stringBuilder = StringBuilder() + stringBuilder.append(mediaCodecInfoWrapper.name) + if (mediaCodecInfoWrapper.name != mediaCodecInfoWrapper.canonicalName) stringBuilder.append(" (${mediaCodecInfoWrapper.canonicalName})") + return stringBuilder } override fun getSummary(): CharSequence { + if (!this::filterSpec.isInitialized) return "awaiting ${FilterSpec::class.simpleName}" val summaryBuilder = StringBuilder() summaryBuilder.append( String.format(context.getString(R.string.supported_types), mediaCodecInfoWrapper.supportedTypes.contentToString()) ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - summaryBuilder.append("\n") - summaryBuilder.append( - String.format(context.getString(R.string.hardware_accelerated), mediaCodecInfoWrapper.isHardwareAccelerated) - ) - summaryBuilder.append("\n") - summaryBuilder.append(String.format(context.getString(R.string.software_only), mediaCodecInfoWrapper.isSoftwareOnly)) - if (SHOW_ALIASES) { + if (filterSpec.showHardwareCodecs && filterSpec.showSoftwareCodecs) { + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.hardware_accelerated), mediaCodecInfoWrapper.isHardwareAccelerated)) + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.software_only), mediaCodecInfoWrapper.isSoftwareOnly)) + } + if (filterSpec.showAliases) { summaryBuilder.append("\n") summaryBuilder.append(String.format(context.getString(R.string.alias), mediaCodecInfoWrapper.isAlias)) } @@ -60,4 +62,12 @@ class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCo } return summaryBuilder } + + data class FilterSpec( + val showAliases: Boolean, + val showVideoCodecs: Boolean, + val showAudioCodecs: Boolean, + @RequiresApi(Build.VERSION_CODES.Q) val showHardwareCodecs: Boolean, + @RequiresApi(Build.VERSION_CODES.Q) val showSoftwareCodecs: Boolean, + ) } diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt index 67a8eb5..3621728 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt @@ -3,9 +3,16 @@ package com.programminghoch10.CodecMod import android.media.MediaCodecList import android.os.Build import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import androidx.fragment.app.FragmentActivity +import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat +import androidx.preference.children +import com.programminghoch10.CodecMod.CodecStore.Companion.DEFAULT_VALUE class SettingsActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -18,12 +25,64 @@ class SettingsActivity : FragmentActivity() { } class SettingsFragment : PreferenceFragmentCompat() { + lateinit var menu: Menu + + val decodersPreferenceCategory get() = findPreference("category_decoders")!! + val encodersPreferenceCategory get() = findPreference("category_encoders")!! + val allHiddenByFiltersPreference get() = findPreference("allHiddenByFilters")!! + + val allPreferences get() = (encodersPreferenceCategory.children + decodersPreferenceCategory.children).filterIsInstance(MediaCodecPreference::class.java) + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.settings_options, menu) + this.menu = menu + reevaluateFilters() + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId in listOf( + R.id.show_aliases, + R.id.show_video_codecs, + R.id.show_audio_codecs, + R.id.show_hardware_codecs, + R.id.show_software_codecs, + ) + ) { + item.isChecked = !item.isChecked + reevaluateFilters() + return true + } + if (item.itemId in listOf(R.id.enable_all_visible, R.id.disable_all_visible)) { + val checked = item.itemId == R.id.enable_all_visible + allPreferences.forEach { it.isChecked = checked } + return true + } + if (item.itemId == R.id.reset_configuration) { + allPreferences.forEach { it.isChecked = DEFAULT_VALUE } + return true + } + return super.onOptionsItemSelected(item) + } + + fun reevaluateFilters() { + val filterSpec = MediaCodecPreference.FilterSpec( + menu.findItem(R.id.show_aliases).isChecked, + menu.findItem(R.id.show_video_codecs).isChecked, + menu.findItem(R.id.show_audio_codecs).isChecked, + menu.findItem(R.id.show_hardware_codecs).isChecked, + menu.findItem(R.id.show_software_codecs).isChecked, + ) + Log.d(this::class.java.simpleName, "reevaluateFilters: $filterSpec") + allPreferences.forEach { it.setFilterSpec(filterSpec) } + allHiddenByFiltersPreference.isVisible = allPreferences.all { !it.isVisible } + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) + setHasOptionsMenu(true) //getPreferenceManager().setSharedPreferencesName("codecs"); val codecStore = CodecStore(requireContext()) - val decodersPreferenceCategory = findPreference("category_decoders")!! - val encodersPreferenceCategory = findPreference("category_encoders")!! var mediaCodecs: List if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -40,15 +99,11 @@ class SettingsActivity : FragmentActivity() { ) } for (mediaCodecInfo in mediaCodecs) { - if (mediaCodecInfo.isAlias && !SHOW_ALIASES) continue val preference = MediaCodecPreference(requireContext(), codecStore, mediaCodecInfo) + preference.isIconSpaceReserved = false val preferenceCategory = if (mediaCodecInfo.isEncoder) encodersPreferenceCategory else decodersPreferenceCategory preferenceCategory.addPreference(preference) } } - - companion object { - const val SHOW_ALIASES = true - } } } diff --git a/CodecMod/src/main/res/menu/settings_options.xml b/CodecMod/src/main/res/menu/settings_options.xml new file mode 100644 index 0000000..56062fc --- /dev/null +++ b/CodecMod/src/main/res/menu/settings_options.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/CodecMod/src/main/res/values/strings.xml b/CodecMod/src/main/res/values/strings.xml index ec4ae55..be0aa5f 100644 --- a/CodecMod/src/main/res/values/strings.xml +++ b/CodecMod/src/main/res/values/strings.xml @@ -1,11 +1,21 @@ CodecMod - This Module allows you to selectively disable audio/video hardware/software encoders/decoders. - + Selectively disable audio/video hardware/software encoders/decoders. CodecMod Settings Hardware-accelerated: %b Software-only: %b Supported MIME-Types: %s Alias: %b Vendor: %b + Show Video Codecs + Show Audio Codecs + Show Hardware Codecs + Show Software Codecs + Show Aliases + Enable all visible codecs + Disable all visible codecs + Reset Configuration + All Codecs are hidden by the current filter settings. + Decoders + Encoders diff --git a/CodecMod/src/main/res/xml/root_preferences.xml b/CodecMod/src/main/res/xml/root_preferences.xml index de52ff1..722ca88 100644 --- a/CodecMod/src/main/res/xml/root_preferences.xml +++ b/CodecMod/src/main/res/xml/root_preferences.xml @@ -1,9 +1,21 @@ + From bdd29124cad7204c1b084d51bc17867685242e2d Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:23:06 +0200 Subject: [PATCH 5/5] CodecMod: add tabs for encoders and decoders --- CodecMod/build.gradle.kts | 1 + .../CodecMod/MediaCodecPreference.kt | 4 ++ .../CodecMod/SettingsActivity.kt | 47 ++++++++++++------- .../src/main/res/color-v21/tab_text_color.xml | 6 +++ .../src/main/res/color/tab_text_color.xml | 6 +++ .../src/main/res/layout/settings_activity.xml | 38 ++++++++++++++- CodecMod/src/main/res/values-v21/themes.xml | 3 +- CodecMod/src/main/res/values/themes.xml | 9 +++- .../src/main/res/xml/root_preferences.xml | 21 --------- 9 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 CodecMod/src/main/res/color-v21/tab_text_color.xml create mode 100644 CodecMod/src/main/res/color/tab_text_color.xml delete mode 100644 CodecMod/src/main/res/xml/root_preferences.xml diff --git a/CodecMod/build.gradle.kts b/CodecMod/build.gradle.kts index 5c6891a..9d46086 100644 --- a/CodecMod/build.gradle.kts +++ b/CodecMod/build.gradle.kts @@ -13,6 +13,7 @@ android { } compileOptions { isCoreLibraryDesugaringEnabled = true + viewBinding.isEnabled = true } } diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt index df98f97..1c5abd2 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt @@ -16,6 +16,8 @@ class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCo fun setFilterSpec(filterSpec: FilterSpec) { this.filterSpec = filterSpec isVisible = when { + !filterSpec.showDecoders && !mediaCodecInfoWrapper.isEncoder -> false + !filterSpec.showEncoders && mediaCodecInfoWrapper.isEncoder -> false !filterSpec.showAliases && mediaCodecInfoWrapper.isAlias -> false !filterSpec.showVideoCodecs && mediaCodecInfoWrapper.isVideoCodec -> false !filterSpec.showAudioCodecs && mediaCodecInfoWrapper.isAudioCodec -> false @@ -64,6 +66,8 @@ class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCo } data class FilterSpec( + val showEncoders: Boolean, + val showDecoders: Boolean, val showAliases: Boolean, val showVideoCodecs: Boolean, val showAudioCodecs: Boolean, diff --git a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt index 3621728..af8f9e9 100644 --- a/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt @@ -7,31 +7,37 @@ import android.util.Log import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View +import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity -import androidx.preference.Preference -import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.children import com.programminghoch10.CodecMod.CodecStore.Companion.DEFAULT_VALUE +import com.programminghoch10.CodecMod.databinding.SettingsActivityBinding class SettingsActivity : FragmentActivity() { + private lateinit var binding: SettingsActivityBinding + val allHiddenByFiltersView get() = binding.allHiddenByFilters + val buttonCategoryDecoders get() = binding.buttonCategoryDecoders + val buttonCategoryEncoders get() = binding.buttonCategoryEncoders + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.settings_activity) + binding = SettingsActivityBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) if (savedInstanceState == null) { supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit() } actionBar?.setDisplayHomeAsUpEnabled(supportFragmentManager.backStackEntryCount > 0) + buttonCategoryDecoders.isActivated = true + buttonCategoryEncoders.isActivated = false } class SettingsFragment : PreferenceFragmentCompat() { lateinit var menu: Menu + val activity get() = requireActivity() as SettingsActivity - val decodersPreferenceCategory get() = findPreference("category_decoders")!! - val encodersPreferenceCategory get() = findPreference("category_encoders")!! - val allHiddenByFiltersPreference get() = findPreference("allHiddenByFilters")!! - - val allPreferences get() = (encodersPreferenceCategory.children + decodersPreferenceCategory.children).filterIsInstance(MediaCodecPreference::class.java) + val allMediaCodecPreferences get() = preferenceScreen.children.filterIsInstance(MediaCodecPreference::class.java) override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.settings_options, menu) @@ -55,11 +61,11 @@ class SettingsActivity : FragmentActivity() { } if (item.itemId in listOf(R.id.enable_all_visible, R.id.disable_all_visible)) { val checked = item.itemId == R.id.enable_all_visible - allPreferences.forEach { it.isChecked = checked } + allMediaCodecPreferences.filter { it.isVisible }.forEach { it.isChecked = checked } return true } if (item.itemId == R.id.reset_configuration) { - allPreferences.forEach { it.isChecked = DEFAULT_VALUE } + allMediaCodecPreferences.forEach { it.isChecked = DEFAULT_VALUE } return true } return super.onOptionsItemSelected(item) @@ -67,6 +73,8 @@ class SettingsActivity : FragmentActivity() { fun reevaluateFilters() { val filterSpec = MediaCodecPreference.FilterSpec( + activity.buttonCategoryEncoders.isActivated, + activity.buttonCategoryDecoders.isActivated, menu.findItem(R.id.show_aliases).isChecked, menu.findItem(R.id.show_video_codecs).isChecked, menu.findItem(R.id.show_audio_codecs).isChecked, @@ -74,16 +82,24 @@ class SettingsActivity : FragmentActivity() { menu.findItem(R.id.show_software_codecs).isChecked, ) Log.d(this::class.java.simpleName, "reevaluateFilters: $filterSpec") - allPreferences.forEach { it.setFilterSpec(filterSpec) } - allHiddenByFiltersPreference.isVisible = allPreferences.all { !it.isVisible } + allMediaCodecPreferences.forEach { it.setFilterSpec(filterSpec) } + activity.allHiddenByFiltersView.isVisible = allMediaCodecPreferences.all { !it.isVisible } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.root_preferences, rootKey) + preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) setHasOptionsMenu(true) - //getPreferenceManager().setSharedPreferencesName("codecs"); val codecStore = CodecStore(requireContext()) + val onCategoryChangeListener = View.OnClickListener { + activity.buttonCategoryDecoders.isActivated = false + activity.buttonCategoryEncoders.isActivated = false + it.isActivated = true + reevaluateFilters() + } + activity.buttonCategoryDecoders.setOnClickListener(onCategoryChangeListener) + activity.buttonCategoryEncoders.setOnClickListener(onCategoryChangeListener) + var mediaCodecs: List if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS) @@ -101,8 +117,7 @@ class SettingsActivity : FragmentActivity() { for (mediaCodecInfo in mediaCodecs) { val preference = MediaCodecPreference(requireContext(), codecStore, mediaCodecInfo) preference.isIconSpaceReserved = false - val preferenceCategory = if (mediaCodecInfo.isEncoder) encodersPreferenceCategory else decodersPreferenceCategory - preferenceCategory.addPreference(preference) + preferenceScreen.addPreference(preference) } } } diff --git a/CodecMod/src/main/res/color-v21/tab_text_color.xml b/CodecMod/src/main/res/color-v21/tab_text_color.xml new file mode 100644 index 0000000..ac1d710 --- /dev/null +++ b/CodecMod/src/main/res/color-v21/tab_text_color.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/CodecMod/src/main/res/color/tab_text_color.xml b/CodecMod/src/main/res/color/tab_text_color.xml new file mode 100644 index 0000000..e061629 --- /dev/null +++ b/CodecMod/src/main/res/color/tab_text_color.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/CodecMod/src/main/res/layout/settings_activity.xml b/CodecMod/src/main/res/layout/settings_activity.xml index 0b6efae..d764790 100644 --- a/CodecMod/src/main/res/layout/settings_activity.xml +++ b/CodecMod/src/main/res/layout/settings_activity.xml @@ -4,11 +4,47 @@ style="@style/AppTheme.Edge2EdgeFix" android:layout_width="match_parent" android:layout_height="match_parent" + android:animateLayoutChanges="true" + android:orientation="vertical" > + + +