diff --git a/CodecMod/Readme.md b/CodecMod/Readme.md
new file mode 100644
index 0000000..1beccc7
--- /dev/null
+++ b/CodecMod/Readme.md
@@ -0,0 +1,5 @@
+# CodecMod
+
+This Module allows you to selectively disable audio/video hardware/software encoders/decoders.
+
+Supports all codecs reported by Android through the MediaCodecList API.
diff --git a/CodecMod/build.gradle.kts b/CodecMod/build.gradle.kts
new file mode 100644
index 0000000..c99cfe2
--- /dev/null
+++ b/CodecMod/build.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ alias(libs.plugins.buildlogic.android.application)
+}
+
+android {
+ namespace = "com.programminghoch10.CodecMod"
+
+ defaultConfig {
+ minSdk = 16
+ targetSdk = 35
+ }
+ compileOptions {
+ isCoreLibraryDesugaringEnabled = true
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.preference)
+ coreLibraryDesugaring(libs.android.desugarJdkLibs)
+}
diff --git a/CodecMod/src/main/AndroidManifest.xml b/CodecMod/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b33a41b
--- /dev/null
+++ b/CodecMod/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CodecMod/src/main/assets/xposed_init b/CodecMod/src/main/assets/xposed_init
new file mode 100644
index 0000000..696169f
--- /dev/null
+++ b/CodecMod/src/main/assets/xposed_init
@@ -0,0 +1 @@
+com.programminghoch10.CodecMod.Hook
diff --git a/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java b/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java
new file mode 100644
index 0000000..a1d86c2
--- /dev/null
+++ b/CodecMod/src/main/java/com/programminghoch10/CodecMod/CodecStore.java
@@ -0,0 +1,80 @@
+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";
+ SharedPreferences sharedPreferences;
+ List receivers = new LinkedList<>();
+
+ @SuppressLint("WorldReadableFiles")
+ 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
new file mode 100644
index 0000000..a3df051
--- /dev/null
+++ b/CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java
@@ -0,0 +1,89 @@
+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) throws Throwable {
+ 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) throws Throwable {
+ return (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos().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 = (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
new file mode 100644
index 0000000..29c340b
--- /dev/null
+++ b/CodecMod/src/main/java/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.java
@@ -0,0 +1,69 @@
+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(api = Build.VERSION_CODES.Q)
+ public boolean isHardwareAccelerated() {
+ return mediaCodecInfo.isHardwareAccelerated();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ public boolean isSoftwareOnly() {
+ return mediaCodecInfo.isSoftwareOnly();
+ }
+
+ @RequiresApi(api = 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
new file mode 100644
index 0000000..589bff8
--- /dev/null
+++ b/CodecMod/src/main/java/com/programminghoch10/CodecMod/SettingsActivity.java
@@ -0,0 +1,91 @@
+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/res/layout/settings_activity.xml b/CodecMod/src/main/res/layout/settings_activity.xml
new file mode 100644
index 0000000..89fbea7
--- /dev/null
+++ b/CodecMod/src/main/res/layout/settings_activity.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/CodecMod/src/main/res/values-v21/themes.xml b/CodecMod/src/main/res/values-v21/themes.xml
new file mode 100644
index 0000000..6ee0359
--- /dev/null
+++ b/CodecMod/src/main/res/values-v21/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/CodecMod/src/main/res/values-v35/themes.xml b/CodecMod/src/main/res/values-v35/themes.xml
new file mode 100644
index 0000000..1cdd3f2
--- /dev/null
+++ b/CodecMod/src/main/res/values-v35/themes.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/CodecMod/src/main/res/values/strings.xml b/CodecMod/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ec4ae55
--- /dev/null
+++ b/CodecMod/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ CodecMod
+ This Module allows you to selectively disable audio/video hardware/software encoders/decoders.
+
+ CodecMod Settings
+ Hardware-accelerated: %b
+ Software-only: %b
+ Supported MIME-Types: %s
+ Alias: %b
+ Vendor: %b
+
diff --git a/CodecMod/src/main/res/values/themes.xml b/CodecMod/src/main/res/values/themes.xml
new file mode 100644
index 0000000..33bc3e6
--- /dev/null
+++ b/CodecMod/src/main/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/CodecMod/src/main/res/xml/root_preferences.xml b/CodecMod/src/main/res/xml/root_preferences.xml
new file mode 100644
index 0000000..db14437
--- /dev/null
+++ b/CodecMod/src/main/res/xml/root_preferences.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/README.md b/README.md
index 6a89956..b43c5f7 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ A collection of small Xposed Modules.
| [AutomaticAdvancedSettingsExpander](AutomaticAdvancedSettingsExpander) | [@binarynoise](https://github.com/binarynoise) | Automatically expands the advanced settings in the Settings app | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=AutomaticAdvancedSettingsExpander) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.AutomaticAdvancedSettingsExpander) |
| [BetterBluetoothDeviceSort](BetterBluetoothDeviceSort) | [@binarynoise](https://github.com/binarynoise) | Sorts Bluetooth devices by name | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=betterBluetoothDeviceSort) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.betterBluetoothDeviceSort) |
| [BetterVerboseWiFiLogging](BetterVerboseWiFiLogging) | [@binarynoise](https://github.com/binarynoise) | Makes the verbose Wi-Fi information more readable | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=betterVerboseWiFiLogging) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.betterVerboseWiFiLogging) |
+| [CodecMod](CodecMod) | [@programminghoch10](https://github.com/programminghoch10) | Selectively disable audio/video hardware/software encoders/decoders. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=CodecMod) |
| [Don'tResetIfBootedAndConnected](DontResetIfBootedAndConnected) | [@binarynoise](https://github.com/binarynoise) | | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=dontResetIfBootedAndConnected) |
| [FreeNotifications](FreeNotifications) | [@binarynoise](https://github.com/binarynoise) | Enables customization for all Notification Channels again | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=freeNotifications) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.freeNotifications) |
| [MotionEventMod](MotionEventMod) | [@programminghoch10](https://github.com/programminghoch10) | Disable touch input for some seconds after the stylus was in use | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=MotionEventMod) |
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8fab462..f77f849 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,6 +11,7 @@ hiddenapibypass = "5.0"
jebrainsAnnotations = "26.0.2"
kotlin = "2.2.0"
xposed = "82"
+preference = "1.2.1"
[libraries]
android-desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
@@ -19,6 +20,7 @@ android-tools-common = { module = "com.android.tools:common", version.ref = "and
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidxAnnotation" }
androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" }
androidx-collection-ktx = { module = "androidx.collection:collection-ktx", version.ref = "collectionKtx" }
+androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
github-api = { module = "org.kohsuke:github-api", version.ref = "githubApi" }
hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" }
jebtrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jebrainsAnnotations" }
diff --git a/metadata/com.programminghoch10.CodecMod/en-US/full_description.txt b/metadata/com.programminghoch10.CodecMod/en-US/full_description.txt
new file mode 100644
index 0000000..90d04bd
--- /dev/null
+++ b/metadata/com.programminghoch10.CodecMod/en-US/full_description.txt
@@ -0,0 +1,3 @@
+This Module allows you to selectively disable codecs on your android device.
+Every encoder and decoder is listed and can be selectively disabled at choice.
+Supports all codecs reported by Android through the MediaCodecList API.
diff --git a/metadata/com.programminghoch10.CodecMod/en-US/short_description.txt b/metadata/com.programminghoch10.CodecMod/en-US/short_description.txt
new file mode 100644
index 0000000..40da116
--- /dev/null
+++ b/metadata/com.programminghoch10.CodecMod/en-US/short_description.txt
@@ -0,0 +1 @@
+This Module allows you to selectively disable audio/video hardware/software encoders/decoders.
diff --git a/metadata/com.programminghoch10.CodecMod/en-US/title.txt b/metadata/com.programminghoch10.CodecMod/en-US/title.txt
new file mode 100644
index 0000000..2826e5e
--- /dev/null
+++ b/metadata/com.programminghoch10.CodecMod/en-US/title.txt
@@ -0,0 +1 @@
+CodecMod
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 931746a..f6bef72 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -48,6 +48,7 @@ include(":BetterAnimationScaling")
include(":BetterVerboseWiFiLogging")
include(":BetterBluetoothDeviceSort")
include(":ClassHunter")
+include(":CodecMod")
include(":DontResetIfBootedAndConnected")
include(":FreeNotifications")
include(":MotionEventMod")