diff --git a/CodecMod/build.gradle.kts b/CodecMod/build.gradle.kts index 2f78ef6..9d46086 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 { @@ -7,15 +8,16 @@ android { defaultConfig { minSdk = 16 - targetSdk = 35 + targetSdk = 37 multiDexEnabled = true } compileOptions { isCoreLibraryDesugaringEnabled = true + viewBinding.isEnabled = true } } 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 29c340b..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(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 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..c9cf659 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/CodecStore.kt @@ -0,0 +1,72 @@ +package com.programminghoch10.CodecMod + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.preference.PreferenceDataStore +import de.robv.android.xposed.XSharedPreferences + +class CodecStore : PreferenceDataStore { + private val sharedPreferences: SharedPreferences + private val receivers: MutableList = mutableListOf() + + constructor(context: Context) { + sharedPreferences = context.getSharedPreferences(PREFERENCES, Context.MODE_WORLD_READABLE) + } + + constructor() { + sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES) + } + + fun getCodecPreference(mediaCodecInfo: MediaCodecInfoWrapper): Boolean { + return getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE) + } + + 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(key, value) + } + } + dispatchOnCodecPreferenceChanged(key, value) + } + + fun registerOnCodecPreferenceChangedListener( + mediaCodecInfo: MediaCodecInfoWrapper, + onCodecPreferenceChangedListener: OnCodecPreferenceChangedListener, + ) { + val listener = OnCodecPreferenceChangedListenerMeta( + mediaCodecInfo, + onCodecPreferenceChangedListener, + ) + receivers.add(listener) + } + + 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 data class OnCodecPreferenceChangedListenerMeta( + val mediaCodecInfo: MediaCodecInfoWrapper, + val callback: OnCodecPreferenceChangedListener, + ) + + 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..cf7a046 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecInfoWrapper.kt @@ -0,0 +1,52 @@ +package com.programminghoch10.CodecMod + +import android.annotation.SuppressLint +import android.annotation.TargetApi +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 + + @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 + + 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() + + 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 new file mode 100644 index 0000000..1c5abd2 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/MediaCodecPreference.kt @@ -0,0 +1,77 @@ +package com.programminghoch10.CodecMod + +import android.content.Context +import android.media.MediaCodecInfo +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.preference.SwitchPreference + +class MediaCodecPreference(context: Context, codecStore: CodecStore, val mediaCodecInfoWrapper: MediaCodecInfoWrapper) : SwitchPreference(context) { + + constructor(context: Context, codecStore: CodecStore, mediaCodecInfo: MediaCodecInfo) : this( + context, codecStore, MediaCodecInfoWrapper(mediaCodecInfo) + ) + + private lateinit var filterSpec: FilterSpec + 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 + 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 { + preferenceDataStore = codecStore + setDefaultValue(CodecStore.DEFAULT_VALUE) + key = CodecStore.getKey(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) { + 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)) + } + summaryBuilder.append("\n") + summaryBuilder.append(String.format(context.getString(R.string.vendor), mediaCodecInfoWrapper.isVendor)) + } + return summaryBuilder + } + + data class FilterSpec( + val showEncoders: Boolean, + val showDecoders: Boolean, + 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 new file mode 100644 index 0000000..af8f9e9 --- /dev/null +++ b/CodecMod/src/main/kotlin/com/programminghoch10/CodecMod/SettingsActivity.kt @@ -0,0 +1,124 @@ +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 android.view.View +import androidx.core.view.isVisible +import androidx.fragment.app.FragmentActivity +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) + 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 allMediaCodecPreferences get() = preferenceScreen.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 + allMediaCodecPreferences.filter { it.isVisible }.forEach { it.isChecked = checked } + return true + } + if (item.itemId == R.id.reset_configuration) { + allMediaCodecPreferences.forEach { it.isChecked = DEFAULT_VALUE } + return true + } + return super.onOptionsItemSelected(item) + } + + 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, + menu.findItem(R.id.show_hardware_codecs).isChecked, + menu.findItem(R.id.show_software_codecs).isChecked, + ) + Log.d(this::class.java.simpleName, "reevaluateFilters: $filterSpec") + allMediaCodecPreferences.forEach { it.setFilterSpec(filterSpec) } + activity.allHiddenByFiltersView.isVisible = allMediaCodecPreferences.all { !it.isVisible } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) + setHasOptionsMenu(true) + 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) + mediaCodecs = mediaCodecList.codecInfos.map { mediaCodecInfo -> MediaCodecInfoWrapper(mediaCodecInfo) } + } else { + mediaCodecs = mutableListOf() + for (i in 0.. + + + + 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" > + + +