Skip to content

Commit 147d066

Browse files
Initial working version of CodecMod
1 parent 620f0ff commit 147d066

File tree

11 files changed

+273
-0
lines changed

11 files changed

+273
-0
lines changed

CodecMod/build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
alias(libs.plugins.buildlogic.android.application)
3+
}
4+
5+
android {
6+
namespace = "com.programminghoch10.CodecMod"
7+
8+
defaultConfig {
9+
minSdk = 29
10+
targetSdk = 35
11+
}
12+
}
13+
dependencies {
14+
implementation(libs.androidx.preference)
15+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application android:label="@string/app_name">
5+
<activity
6+
android:name=".SettingsActivity"
7+
android:exported="true"
8+
android:label="@string/title_activity_settings"
9+
android:theme="@android:style/Theme.DeviceDefault.Settings">
10+
<intent-filter>
11+
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
12+
<category android:name="android.intent.category.DEFAULT" />
13+
<action android:name="android.intent.action.MAIN" />
14+
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
15+
</intent-filter>
16+
</activity>
17+
18+
<meta-data
19+
android:name="xposedmodule"
20+
android:value="true" />
21+
<meta-data
22+
android:name="xposeddescription"
23+
android:value="@string/app_name" />
24+
<meta-data
25+
android:name="xposedminversion"
26+
android:value="93" />
27+
</application>
28+
29+
</manifest>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.programminghoch10.CodecMod.Hook
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import static android.content.Context.MODE_WORLD_READABLE;
4+
5+
import android.content.Context;
6+
import android.content.SharedPreferences;
7+
import android.media.MediaCodecInfo;
8+
9+
import java.util.LinkedList;
10+
import java.util.List;
11+
12+
import de.robv.android.xposed.XSharedPreferences;
13+
14+
public class CodecStore {
15+
static final boolean DEFAULT_VALUE = true;
16+
private static final boolean REMOVE_DEFAULT_VALUE_FROM_CONFIG = true;
17+
private static final String PREFERENCES = "codecs";
18+
SharedPreferences sharedPreferences;
19+
List<OnCodecPreferenceChangedListenerMeta> receivers = new LinkedList<>();
20+
21+
CodecStore(Context context) {
22+
this.sharedPreferences = context.getSharedPreferences(PREFERENCES, MODE_WORLD_READABLE);
23+
}
24+
25+
CodecStore() {
26+
this.sharedPreferences = new XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES);
27+
}
28+
29+
static String getKey(MediaCodecInfo mediaCodecInfo) {
30+
return "codec_" + mediaCodecInfo.getCanonicalName();
31+
}
32+
33+
boolean getCodecPreference(MediaCodecInfo mediaCodecInfo) {
34+
return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE);
35+
}
36+
37+
boolean setCodecPreference(MediaCodecInfo mediaCodecInfo, boolean enabled) {
38+
boolean success = false;
39+
if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) {
40+
success = sharedPreferences.edit().remove(getKey(mediaCodecInfo)).commit();
41+
} else {
42+
success = sharedPreferences.edit().putBoolean(getKey(mediaCodecInfo), enabled).commit();
43+
}
44+
if (!success)
45+
return success;
46+
dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled);
47+
return success;
48+
}
49+
50+
void registerOnCodecPreferenceChangedListener(MediaCodecInfo mediaCodecInfo, OnCodecPreferenceChangedListener onCodecPreferenceChangedListener) {
51+
OnCodecPreferenceChangedListenerMeta listener = new OnCodecPreferenceChangedListenerMeta();
52+
listener.mediaCodecInfo = mediaCodecInfo;
53+
listener.callback = onCodecPreferenceChangedListener;
54+
receivers.add(listener);
55+
}
56+
57+
private void dispatchOnCodecPreferenceChanged(MediaCodecInfo mediaCodecInfo, boolean enabled) {
58+
receivers.stream()
59+
.filter(r -> getKey(r.mediaCodecInfo).equals(getKey(mediaCodecInfo)))
60+
.forEach(r -> r.callback.onCodecPreferenceChanged(enabled));
61+
}
62+
63+
interface OnCodecPreferenceChangedListener {
64+
void onCodecPreferenceChanged(boolean value);
65+
}
66+
67+
private class OnCodecPreferenceChangedListenerMeta {
68+
MediaCodecInfo mediaCodecInfo;
69+
OnCodecPreferenceChangedListener callback;
70+
}
71+
72+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import android.media.MediaCodecInfo;
4+
import android.media.MediaCodecList;
5+
6+
import java.util.Arrays;
7+
8+
import de.robv.android.xposed.IXposedHookLoadPackage;
9+
import de.robv.android.xposed.XC_MethodReplacement;
10+
import de.robv.android.xposed.XposedBridge;
11+
import de.robv.android.xposed.XposedHelpers;
12+
import de.robv.android.xposed.callbacks.XC_LoadPackage;
13+
14+
public class Hook implements IXposedHookLoadPackage {
15+
@Override
16+
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
17+
if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) return;
18+
if (lpparam.packageName.equals("android")) {
19+
// system-wide hooking not implemented
20+
return;
21+
}
22+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfos", new XC_MethodReplacement() {
23+
@Override
24+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
25+
MediaCodecInfo[] mediaCodecInfos = (MediaCodecInfo[]) XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
26+
if (mediaCodecInfos.length == 0) return mediaCodecInfos;
27+
CodecStore codecStore = new CodecStore();
28+
MediaCodecInfo[] filteredMediaCodecInfos = Arrays.stream(mediaCodecInfos)
29+
.filter(codecStore::getCodecPreference)
30+
.toArray(MediaCodecInfo[]::new);
31+
return filteredMediaCodecInfos;
32+
}
33+
});
34+
35+
// reimplementations of deprecated methods for compatibility
36+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() {
37+
@Override
38+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
39+
return (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos().length;
40+
}
41+
});
42+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() {
43+
@Override
44+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
45+
final int position = (int) param.args[0];
46+
MediaCodecInfo[] mediaCodecInfos = (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos();
47+
if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException();
48+
return mediaCodecInfos[position];
49+
}
50+
});
51+
}
52+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import android.app.ActionBar;
4+
import android.media.MediaCodecInfo;
5+
import android.media.MediaCodecList;
6+
import android.os.Bundle;
7+
8+
import androidx.fragment.app.FragmentActivity;
9+
import androidx.preference.PreferenceCategory;
10+
import androidx.preference.PreferenceFragmentCompat;
11+
import androidx.preference.SwitchPreference;
12+
13+
import java.util.Arrays;
14+
15+
public class SettingsActivity extends FragmentActivity {
16+
@Override
17+
protected void onCreate(Bundle savedInstanceState) {
18+
super.onCreate(savedInstanceState);
19+
setContentView(R.layout.settings_activity);
20+
if (savedInstanceState == null) {
21+
getSupportFragmentManager()
22+
.beginTransaction()
23+
.replace(R.id.settings, new SettingsFragment())
24+
.commit();
25+
}
26+
ActionBar actionBar = getActionBar();
27+
if (actionBar != null) {
28+
actionBar.setDisplayHomeAsUpEnabled(
29+
getSupportFragmentManager().getBackStackEntryCount() > 0
30+
);
31+
}
32+
}
33+
34+
public static class SettingsFragment extends PreferenceFragmentCompat {
35+
private static final boolean SHOW_ALIASES = true;
36+
CodecStore codecStore = null;
37+
38+
@Override
39+
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
40+
setPreferencesFromResource(R.xml.root_preferences, rootKey);
41+
//getPreferenceManager().setSharedPreferencesName("codecs");
42+
CodecStore codecStore = new CodecStore(getContext());
43+
PreferenceCategory decodersPreferenceCategory = findPreference("category_decoders");
44+
PreferenceCategory encodersPreferenceCategory = findPreference("category_encoders");
45+
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
46+
for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) {
47+
if (mediaCodecInfo.isAlias() && !SHOW_ALIASES) continue;
48+
SwitchPreference preference = new SwitchPreference(getContext());
49+
preference.setPersistent(false);
50+
preference.setDefaultValue(CodecStore.DEFAULT_VALUE);
51+
preference.setKey(CodecStore.getKey(mediaCodecInfo));
52+
preference.setOnPreferenceChangeListener((p, n) -> codecStore.setCodecPreference(mediaCodecInfo, (Boolean) n));
53+
codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfo, value -> {
54+
if (preference.isChecked() != value) preference.setChecked(value);
55+
});
56+
preference.setTitle(mediaCodecInfo.getName()
57+
+ (mediaCodecInfo.getName().equals(mediaCodecInfo.getCanonicalName()) ? "" : " (" + mediaCodecInfo.getCanonicalName() + ")"));
58+
preference.setSummary(
59+
String.format(getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated())
60+
+ "\n" +
61+
String.format(getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly())
62+
+ "\n" +
63+
String.format(getString(R.string.supported_types), Arrays.toString(mediaCodecInfo.getSupportedTypes())) +
64+
(SHOW_ALIASES ? "\n" +
65+
String.format(getString(R.string.alias), mediaCodecInfo.isAlias()) : "")
66+
+ "\n" +
67+
String.format(getString(R.string.vendor), mediaCodecInfo.isVendor())
68+
);
69+
PreferenceCategory preferenceCategory = mediaCodecInfo.isEncoder() ? encodersPreferenceCategory : decodersPreferenceCategory;
70+
preferenceCategory.addPreference(preference);
71+
preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo));
72+
}
73+
}
74+
}
75+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:layout_width="match_parent"
3+
android:layout_height="match_parent">
4+
5+
<FrameLayout
6+
android:id="@+id/settings"
7+
android:layout_width="match_parent"
8+
android:layout_height="match_parent" />
9+
</LinearLayout>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<resources>
2+
<string name="app_name">CodecMod</string>
3+
<string name="title_activity_settings">CodecMod Settings</string>
4+
<string name="hardware_accelerated">Hardware-accelerated: %b</string>
5+
<string name="software_only">Software-only: %b</string>
6+
<string name="supported_types">Supported MIME-Types: %s</string>
7+
<string name="alias">Alias: %b</string>
8+
<string name="vendor">Vendor: %b</string>
9+
</resources>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
2+
<PreferenceCategory
3+
app:key="category_decoders"
4+
app:title="Decoders" />
5+
<PreferenceCategory
6+
app:key="category_encoders"
7+
app:title="Encoders" />
8+
</PreferenceScreen>

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ hiddenapibypass = "5.0"
1111
jebrainsAnnotations = "26.0.2"
1212
kotlin = "2.2.0"
1313
xposed = "82"
14+
preference = "1.2.1"
1415

1516
[libraries]
1617
android-desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
@@ -27,6 +28,7 @@ kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin" }
2728
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }
2829
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
2930
xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposed" }
31+
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
3032

3133
[plugins]
3234
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }

0 commit comments

Comments
 (0)