Skip to content

Commit

Permalink
新增: 音质音效-耳机状态自动切换原声
Browse files Browse the repository at this point in the history
  • Loading branch information
HChenX committed May 3, 2024
1 parent 369998b commit 8e2c492
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 15 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission
android:name="android.permission.BLUETOOTH_PRIVILEGED"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
Expand Down
28 changes: 15 additions & 13 deletions app/src/main/java/com/sevtinge/hyperceiler/module/app/MiSound.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
/*
* This file is part of HyperCeiler.
* This file is part of HyperCeiler.
* HyperCeiler is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
* HyperCeiler is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* Copyright (C) 2023-2024 HyperCeiler Contributions
*/
* Copyright (C) 2023-2024 HyperCeiler Contributions
*/
package com.sevtinge.hyperceiler.module.app;

import com.sevtinge.hyperceiler.module.base.BaseModule;
import com.sevtinge.hyperceiler.module.base.HookExpand;
import com.sevtinge.hyperceiler.module.hook.misound.BluetoothListener;
import com.sevtinge.hyperceiler.module.hook.misound.IncreaseSamplingRate;

@HookExpand(pkg = "com.miui.misound", isPad = false, tarAndroid = 33)
public class MiSound extends BaseModule {

@Override
public void handleLoadPackage() {
initHook(new BluetoothListener(), mPrefsMap.getBoolean("misound_bluetooth"));
initHook(IncreaseSamplingRate.INSTANCE, mPrefsMap.getBoolean("misound_increase_sampling_rate"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package com.sevtinge.hyperceiler.module.hook.misound;

import android.app.Application;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.provider.Settings;

import androidx.annotation.NonNull;

import com.sevtinge.hyperceiler.module.base.BaseHook;
import com.sevtinge.hyperceiler.module.base.dexkit.DexKit;

import org.luckypray.dexkit.query.FindClass;
import org.luckypray.dexkit.query.FindField;
import org.luckypray.dexkit.query.matchers.ClassMatcher;
import org.luckypray.dexkit.query.matchers.FieldMatcher;
import org.luckypray.dexkit.result.ClassData;
import org.luckypray.dexkit.result.FieldData;

import java.util.UUID;

import de.robv.android.xposed.XposedHelpers;

public class BluetoothListener extends BaseHook {
private static final String TAG = "BluetoothListener";
private static Object miDolby = null;
private static Object miAudio = null;
private static String uuid = "";

@Override
public void init() throws NoSuchMethodException {
uuid = mPrefsMap.getString("misound_bluetooth_uuid", "");
ClassData classData = DexKit.getDexKitBridge().findClass(FindClass.create().matcher(ClassMatcher.create()
.usingStrings("Creating a DolbyAudioEffect to global output mix!"))).singleOrNull();
try {
if (classData == null) {
logE(TAG, "AudioEffect not found");
} else {
findAndHookConstructor(classData.getInstance(lpparam.classLoader),
int.class, int.class,
new MethodHook() {
@Override
protected void after(MethodHookParam param) {
miDolby = param.thisObject;
// logE(TAG, "miDolby: " + miDolby);
}
}
);
}
} catch (ClassNotFoundException e) {
logE(TAG, e);
}
ClassData classData1 = DexKit.getDexKitBridge().findClass(FindClass.create().matcher(ClassMatcher.create()
.usingStrings("android.media.audiofx.MiSound"))).singleOrNull();
try {
if (classData1 == null) {
logE(TAG, "MiSound not found");
} else {
FieldData fieldData = DexKit.getDexKitBridge().findField(FindField.create().matcher(FieldMatcher.create()
.declaredClass(classData1.getInstance(lpparam.classLoader)).type(Object.class)
)).singleOrNull();
if (fieldData == null) {
logE(TAG, "field not found");
} else {
String name = fieldData.getFieldName();
findAndHookConstructor(classData1.getInstance(lpparam.classLoader),
int.class, int.class,
new MethodHook() {
@Override
protected void after(MethodHookParam param) {
miAudio = XposedHelpers.getObjectField(param.thisObject, name);
// logE(TAG, "miAudio: " + miAudio);
}
}
);
}
}
} catch (ClassNotFoundException e) {
logE(TAG, e);
}
findAndHookMethod("com.miui.misound.MiSoundApplication", "onCreate",
new MethodHook() {
@Override
protected void after(MethodHookParam param) {
Application application = (Application) param.thisObject;
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
application.registerReceiver(new Listener(), intentFilter);
}
}
);
// settings get global effect_implementer
}

private static String effectImplementer(Context context) {
return Settings.Global.getString(context.getContentResolver(), "effect_implementer");
}

private static Object getAudioEffect() {
Class<?> AudioEffect = XposedHelpers.findClassIfExists("android.media.audiofx.AudioEffect", ClassLoader.getSystemClassLoader());
if (AudioEffect == null) return null;
return getAudio(AudioEffect);
}

private static Object getMiSound() {
Class<?> MiSound = XposedHelpers.findClassIfExists("android.media.audiofx.MiSound", ClassLoader.getSystemClassLoader());
if (MiSound == null) return null;
return XposedHelpers.newInstance(MiSound, 1, 0);
}

private static boolean hasControl(Object o) {
return (boolean) XposedHelpers.callMethod(o, "hasControl");
}

private static boolean isEnable(Object o) {
return (boolean) XposedHelpers.callMethod(o, "getEnabled");
}

private static void setEnable(Object o, boolean value) {
XposedHelpers.callMethod(o, "setEnabled", value);
}

@NonNull
private static Object getAudio(Class<?> AudioEffect) {
// Class<?> DolbyAudioEffectHelper = findClassIfExists("com.android.server.audio.dolbyeffect.DolbyEffectController$DolbyAudioEffectHelper",
// ClassLoader.getSystemClassLoader());
// logE(TAG, "DolbyAudioEffectHelper: " + DolbyAudioEffectHelper);
// UUID dolby = (UUID) XposedHelpers.getStaticObjectField(
// DolbyAudioEffectHelper, "EFFECT_TYPE_DOLBY_AUDIO_PROCESSING");
UUID EFFECT_TYPE_NULL = (UUID) XposedHelpers.getStaticObjectField(AudioEffect, "EFFECT_TYPE_NULL");
UUID dolby;
if (uuid.isEmpty()) {
dolby = UUID.fromString("9d4921da-8225-4f29-aefa-39537a04bcaa");
} else {
dolby = UUID.fromString(uuid);
}
return XposedHelpers.newInstance(AudioEffect, EFFECT_TYPE_NULL, dolby, 0, 0);
}

public static class Listener extends BroadcastReceiver {
private static Object AudioEffect = null;
private static Object MiSound = null;
private static boolean lastDolby;
private static boolean lastMiui;

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case BluetoothDevice.ACTION_ACL_CONNECTED -> {
init();
lastDolby = setAudio(AudioEffect, miDolby);
lastMiui = setAudio(MiSound, miAudio);
String implementer = effectImplementer(context);
// logE(TAG, "A: " + AudioEffect + " d: " + miDolby + " M: " + MiSound + " a: " + miAudio
// + " co: " + hasControl(AudioEffect) + " co1: " + hasControl(MiSound) +
// " laD: " + lastDolby + " laM: " + lastMiui + " im: " + implementer);
if (implementer != null) {
if ("dolby".equals(implementer)) {
lastDolby = true;
lastMiui = false;
} else if ("misound".equals(implementer)) {
lastDolby = false;
lastMiui = true;
}
}
}
case BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
init();
recoveryAudio(AudioEffect, miDolby, lastDolby);
recoveryAudio(MiSound, miAudio, lastMiui);
// logE(TAG, "A: " + AudioEffect + " d: " + miDolby + " M: " + MiSound + " a: " + miAudio
// + " co: " + hasControl(AudioEffect) + " co1: " + hasControl(MiSound) +
// " laD: " + lastDolby + " laM: " + lastMiui);
}
}
}
}

private static boolean setAudio(Object audio, Object otherAudio) {
boolean last;
if (audio != null) {
if (hasControl(audio)) {
last = isEnable(audio);
setEnable(audio, false);
return last;
} else if (otherAudio != null) {
if (hasControl(otherAudio)) {
last = isEnable(otherAudio);
setEnable(otherAudio, false);
return last;
}
}
}
return false;
}

private static void recoveryAudio(Object audio, Object otherAudio, boolean last) {
if (audio != null) {
if (last != isEnable(audio)) {
if (hasControl(audio)) {
setEnable(audio, last);
} else if (otherAudio != null) {
if (hasControl(otherAudio)) {
setEnable(otherAudio, last);
}
}
}
}
}

private void init() {
if (AudioEffect == null) {
AudioEffect = getAudioEffect();
}
if (MiSound == null) {
MiSound = getMiSound();
}
}
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,9 @@
<string name="market_package_monitor">禁止监控应用安装和删除</string>
<!--音质音效-->
<string name="misound">音质音效</string>
<string name="misound_bluetooth">耳机状态自动切换原声</string>
<string name="misound_bluetooth_uuid">音效效果 UUID 标识符</string>
<string name="misound_bluetooth_uuid_more">杜比等音效标识符,无效果时再填写,自己想办法获取</string>
<string name="misound_increase_sampling_rate">提高音频采样率</string>
<!--小米设置-->
<string name="mi_settings">小米设置</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,9 @@
<string name="market_package_monitor_desc">Prohibit GetApps from monitoring app installation and uninstallation</string>
<!--Mi sound-->
<string name="misound">Earphones</string>
<string name="misound_bluetooth">The headset status automatically switches to the original sound</string>
<string name="misound_bluetooth_uuid">Sound effect UUID identifier</string>
<string name="misound_bluetooth_uuid_more">Dolby and other sound identifiers, fill in when there is no effect, and find a way to get it yourself</string>
<string name="misound_increase_sampling_rate">Increase sampling rate</string>
<!--Mi Settings-->
<string name="mi_settings">Mi Settings</string>
Expand Down
15 changes: 13 additions & 2 deletions app/src/main/res/xml/misound.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
app:myLocation="@string/misound">
<SwitchPreference
android:title="@string/misound_increase_sampling_rate"
android:defaultValue="false"
android:key="prefs_key_misound_bluetooth"
android:title="@string/misound_bluetooth" />

<EditTextPreference
android:dependency="prefs_key_misound_bluetooth"
android:key="prefs_key_misound_bluetooth_uuid"
android:title="@string/misound_bluetooth_uuid"
android:summary="@string/misound_bluetooth_uuid_more" />

<SwitchPreference
android:defaultValue="false"
android:key="prefs_key_misound_increase_sampling_rate"
android:defaultValue="false" />
android:title="@string/misound_increase_sampling_rate" />

</PreferenceScreen>

0 comments on commit 8e2c492

Please sign in to comment.