Implement equalizer presets for EarFun Air S and Air Pro 4

This commit is contained in:
Lars Berning 2025-03-07 21:15:16 +01:00 committed by José Rebelo
parent d9d0573812
commit 10412d8eb7
18 changed files with 544 additions and 173 deletions

View File

@ -4,14 +4,12 @@ import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunSettingsCustomizer;
public abstract class AbstractEarFunCoordinator extends AbstractDeviceCoordinator {
@Override
@ -23,6 +21,11 @@ public abstract class AbstractEarFunCoordinator extends AbstractDeviceCoordinato
return "EarFun";
}
@Override
public ConnectionType getConnectionType() {
return ConnectionType.BT_CLASSIC;
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
@ -43,9 +46,4 @@ public abstract class AbstractEarFunCoordinator extends AbstractDeviceCoordinato
public int getDisabledIconResource() {
return R.drawable.ic_device_nothingear_disabled;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new EarFunSettingsCustomizer(device);
}
}

View File

@ -2,18 +2,25 @@ package nodomain.freeyourgadget.gadgetbridge.devices.earfun;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airpro4.EarFunAirPro4DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airpro4.EarFunAirPro4SettingsCustomizer;
public class EarFunAirPro4Coordinator extends AbstractEarFunCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(EarFunAirPro4Coordinator.class);
@Override
public int getDeviceNameResource() {
return R.string.devicetype_earfun_air_pro_4;
@ -27,12 +34,22 @@ public class EarFunAirPro4Coordinator extends AbstractEarFunCoordinator {
// can't only check with name, because the device name can be changed
// via the device settings, so we use some of the UUIDs available on the device
// and the mac address prefix to hopefully detect this model reliable
String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb";
String[] uuidPrefixes = {"0000180f", "0000180a"};
// and the mac address prefix to hopefully detect this model reliably
String[] uuids = {
"00001101-0000-1000-8000-00805f9b34fb",
"0000111e-0000-1000-8000-00805f9b34fb",
"0000110b-0000-1000-8000-00805f9b34fb",
"0000110e-0000-1000-8000-00805f9b34fb",
"0000eb04-d102-11e1-9b23-00025b00a5a5",
"0000eb06-d102-11e1-9b23-00025b00a5a5",
"0000eb07-d102-11e1-9b23-00025b00a5a5",
"0000eb05-d102-11e1-9b23-00025b00a5a5",
"df21fe2c-2515-4fdb-8886-f12c4d67927c",
"0000180f-0000-1000-8000-00805f9b34fb",
"0000180a-0000-1000-8000-00805f9b34fb"};
boolean allServicesSupported = Arrays.stream(uuidPrefixes)
.map(uuidPrefix -> UUID.fromString(uuidPrefix + UUID_SUFFIX))
boolean allServicesSupported = Arrays.stream(uuids)
.map(UUID::fromString)
.map(candidate::supportsService).allMatch(b -> b);
boolean macAddressMatches = candidate.getMacAddress().toUpperCase().startsWith("70:5A:6F");
@ -63,9 +80,14 @@ public class EarFunAirPro4Coordinator extends AbstractEarFunCoordinator {
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_air_pro_4_headphones);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_10_band_equalizer);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_air_pro_4_gestures);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_device_name);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_10_band_equalizer);
return deviceSpecificSettings;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new EarFunAirPro4SettingsCustomizer(device);
}
}

View File

@ -10,11 +10,13 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airs.EarFunAirSDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airs.EarFunAirSSettingsCustomizer;
public class EarFunAirSCoordinator extends AbstractEarFunCoordinator {
@ -34,12 +36,19 @@ public class EarFunAirSCoordinator extends AbstractEarFunCoordinator {
// can't only check with name, because the device name can be changed
// via the device settings, so we use some of the UUIDs available on the device
// an the mac address prefix to hopefully detect this model reliable
String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb";
String[] uuidPrefixes = {"00001101", "0000111e", "0000110b", "0000110e", "00000000"};
// and the mac address prefix to hopefully detect this model reliably
String[] uuids = {
"00001101-0000-1000-8000-00805f9b34fb",
"0000111e-0000-1000-8000-00805f9b34fb",
"0000110b-0000-1000-8000-00805f9b34fb",
"0000110e-0000-1000-8000-00805f9b34fb",
"0000eb04-d102-11e1-9b23-00025b00a5a5",
"0000eb06-d102-11e1-9b23-00025b00a5a5",
"0000eb07-d102-11e1-9b23-00025b00a5a5",
"0000eb05-d102-11e1-9b23-00025b00a5a5"};
boolean allServicesSupported = Arrays.stream(uuidPrefixes)
.map(uuidPrefix -> UUID.fromString(uuidPrefix + UUID_SUFFIX))
boolean allServicesSupported = Arrays.stream(uuids)
.map(UUID::fromString)
.map(candidate::supportsService).allMatch(b -> b);
boolean macAddressMatches = candidate.getMacAddress().toUpperCase().startsWith("A8:99:DC");
@ -69,9 +78,14 @@ public class EarFunAirSCoordinator extends AbstractEarFunCoordinator {
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_air_s_headphones);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_6_band_equalizer);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_air_s_gestures);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_device_name);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_6_band_equalizer);
return deviceSpecificSettings;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new EarFunAirSSettingsCustomizer(device);
}
}

View File

@ -98,7 +98,7 @@ public class EarFunPacket {
UNIDENTIFIED_0350((short) 0x0350), // 0001 Pro 4
SET_EQUALIZER_BAND((short) 0x0E01, OTHER_VENDOR_ID), // answers with UNIDENTIFIED_0F81
UNIDENTIFIED_0E80((short) 0x0E80, OTHER_VENDOR_ID), // 01
UNIDENTIFIED_0F81((short) 0x0F81, OTHER_VENDOR_ID), // After setting EQ Band 01
RESPONSE_EQUALIZER_BAND((short) 0x0F81, OTHER_VENDOR_ID),
UNIDENTIFIED_1080((short) 0x1080, OTHER_VENDOR_ID), // 0100 -> 0101 if ANC not off
UNIDENTIFIED_1081((short) 0x1081, OTHER_VENDOR_ID), // 01010000 <- otherwise, 0A010000 <- ANC Transparent
UNIDENTIFIED_1082((short) 0x1082, OTHER_VENDOR_ID), // 01010000 -> 01014646

View File

@ -16,6 +16,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarFunPacketEncoder {
private static final Logger LOG = LoggerFactory.getLogger(EarFunPacketEncoder.class);
// the factor to converting equalizer gain between preference value and
// payload byte (int) value
// Gaia uses a factor of 60 to convert to dB and EarFun projects 6 dBs on a slider scale of 10
private static final double EQUALIZER_GAIN_FACTOR = 60 * 0.6;
public static byte[] encodeBatteryReq() {
return joinPackets(
@ -76,7 +80,7 @@ public class EarFunPacketEncoder {
}
public static byte[] encodeSetEqualizerBand(double gainValue, Equalizer.Band band) {
short gain = (short) ((int) Math.round(gainValue * 60) & 0xFFFF);
short gain = (short) ((int) Math.round(gainValue * EQUALIZER_GAIN_FACTOR) & 0xFFFF);
ByteBuffer buf = ByteBuffer.allocate(9);
buf.put(band.bandId);
buf.put((byte) 0xFF);

View File

@ -63,6 +63,9 @@ public class EarFunProtocol extends GBDeviceProtocol {
case REQUEST_RESPONSE_TOUCH_ACTION:
events.add(handleTouchActionInfo(payload));
break;
// do nothing with these, they are returned after each EQ set operation and always return 01
case RESPONSE_EQUALIZER_BAND:
break;
default:
LOG.error("no handler for packet type {}", packet.getCommand().name());
}
@ -92,11 +95,6 @@ public class EarFunProtocol extends GBDeviceProtocol {
@Override
public byte[] encodeSendConfiguration(String config) {
byte[] customConfiguration = encodeSendConfigurationCustomizer(config);
if (customConfiguration != null) {
return customConfiguration;
}
Prefs prefs = getDevicePrefs();
switch (config) {
case PREF_EARFUN_AMBIENT_SOUND_CONTROL:
@ -137,15 +135,6 @@ public class EarFunProtocol extends GBDeviceProtocol {
return null;
}
/**
* Overwrite this in derived classes to do some device specific custom handling of configurations
*
* @return the config, encoded as byte array
*/
public byte[] encodeSendConfigurationCustomizer(String config) {
return null;
}
public byte[] encodeBatteryReq() {
return EarFunPacketEncoder.encodeBatteryReq();
}

View File

@ -3,24 +3,31 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.*;
import android.content.Context;
import android.os.Parcel;
import android.text.InputFilter;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.SeekBarPreference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
@ -47,29 +54,6 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
@Override
public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
if (preference.getKey().equals(PREF_EARFUN_AMBIENT_SOUND_CONTROL)) {
ListPreference listPreferenceAmbientSound = handler.findPreference(PREF_EARFUN_AMBIENT_SOUND_CONTROL);
ListPreference listPreferenceTransparencyMode = handler.findPreference(PREF_EARFUN_TRANSPARENCY_MODE);
ListPreference listPreferenceAncMode = handler.findPreference(PREF_EARFUN_ANC_MODE);
if (listPreferenceAmbientSound == null || listPreferenceTransparencyMode == null || listPreferenceAncMode == null) {
return;
}
switch (listPreferenceAmbientSound.getValue()) {
case "1": // noise cancelling
listPreferenceTransparencyMode.setVisible(false);
listPreferenceAncMode.setVisible(true);
break;
case "2": // transparency
listPreferenceTransparencyMode.setVisible(true);
listPreferenceAncMode.setVisible(false);
break;
default:
listPreferenceTransparencyMode.setVisible(false);
listPreferenceAncMode.setVisible(false);
}
}
}
@Override
@ -103,13 +87,90 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
EditTextPreference editTextDeviceName = handler.findPreference(PREF_EARFUN_DEVICE_NAME);
if (editTextDeviceName != null) {
editTextDeviceName.setOnBindEditTextListener(editText -> {
InputFilter[] filters = new InputFilter[]{new InputFilterLength(25)};
editText.setFilters(filters);
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(25)});
});
editTextDeviceName.setText(device.getName());
}
}
protected void initializeEqualizerPresetListPreference(DeviceSpecificSettingsHandler handler,
Equalizer.EqualizerPreset[] equalizerPresets) {
ListPreference equalizerPresetListPreference = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
if (equalizerPresetListPreference != null) {
List<CharSequence> entries = Arrays.stream(equalizerPresets)
.map(preset -> localizedPresetName(preset, handler.getContext())).collect(Collectors.toList());
// add an additional element for user set custom band adjustments
entries.add(handler.getContext().getString(R.string.redmi_buds_5_pro_equalizer_preset_custom));
CharSequence[] entryValues = IntStream.rangeClosed(0, equalizerPresets.length)
.mapToObj(Integer::toString).toArray(String[]::new);
equalizerPresetListPreference.setEntries(entries.toArray(new CharSequence[0]));
equalizerPresetListPreference.setEntryValues(entryValues);
}
}
private String localizedPresetName(Equalizer.EqualizerPreset preset, Context context) {
if (preset.getLocalizedPresetName() != -1) {
return context.getString(preset.getLocalizedPresetName());
} else {
return preset.getPresetName();
}
}
protected static void onPreferenceChangeEqualizerPreset(DeviceSpecificSettingsHandler handler,
Equalizer.BandConfig[] equalizerBands,
Equalizer.EqualizerPreset[] equalizerPresets) {
ListPreference listPreferenceEqualizerPreset = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
if (listPreferenceEqualizerPreset == null) {
return;
}
try {
int selectedOption = Integer.parseInt(listPreferenceEqualizerPreset.getValue());
if (selectedOption >= equalizerPresets.length || selectedOption < 0) {
return;
}
Equalizer.EqualizerPreset preset = equalizerPresets[selectedOption];
IntStream.range(0, preset.getSettings().length).forEach(index -> {
String key = equalizerBands[index].getKey();
if (key == null) {
return;
}
SeekBarPreference seekBarPreferenceEqualizerBand = handler.findPreference(key);
if (seekBarPreferenceEqualizerBand == null) {
return;
}
int gain = (int) Math.round(preset.getSettings()[index]);
seekBarPreferenceEqualizerBand.setValue(gain);
// call the change listener after setting last band to send new values to the device
if (index == preset.getSettings().length - 1) {
seekBarPreferenceEqualizerBand.callChangeListener(gain);
}
});
} catch (NumberFormatException ignored) {
}
}
protected static int getSelectedPresetFromEqualizerBands(DeviceSpecificSettingsHandler handler,
Equalizer.BandConfig[] equalizerBands,
Equalizer.EqualizerPreset[] equalizerPresets) {
double[] equalizerConfig = Arrays.stream(equalizerBands)
.filter(bandConfig -> bandConfig.getKey() != null)
.map(bandConfig -> {
SeekBarPreference bandSeekBarPreference = handler.findPreference(bandConfig.getKey());
return bandSeekBarPreference.getValue();
})
.mapToDouble(Integer::doubleValue)
.toArray();
return IntStream.range(0, equalizerPresets.length)
.filter(i -> Arrays.equals(equalizerPresets[i].getSettings(), equalizerConfig))
.findFirst()
// if filter settings do not match a preset, select the "custom" preset
.orElse(equalizerPresets.length);
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
@ -123,24 +184,4 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
}
private static class InputFilterLength implements InputFilter {
private final int maxLength;
public InputFilterLength(int maxLength) {
this.maxLength = maxLength;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
int keep = maxLength - (dest.length() - (dend - dstart));
if (keep <= 0) {
return "";
} else if (keep >= end - start) {
return null;
} else {
return source.subSequence(start, start + keep);
}
}
}
}

View File

@ -3,8 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airpro4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunPacketEncoder;
@ -16,18 +14,13 @@ public class EarFunAirPro4Protocol extends EarFunProtocol {
private static final Logger LOG = LoggerFactory.getLogger(EarFunAirPro4Protocol.class);
public byte[] encodeSendConfigurationCustomizer(String config) {
if (containsKey(Equalizer.TenBandEqualizer, config)) {
@Override
public byte[] encodeSendConfiguration(String config) {
if (Equalizer.containsKey(Equalizer.TenBandEqualizer, config)) {
Prefs prefs = getDevicePrefs();
return EarFunPacketEncoder.encodeSetEqualizerTenBands(prefs);
}
return null;
}
private static boolean containsKey(Equalizer.BandConfig[] array, String key) {
return Arrays.stream(array)
.anyMatch(element -> element.key.equals(key));
return super.encodeSendConfiguration(config);
}
protected EarFunAirPro4Protocol(GBDevice device) {

View File

@ -0,0 +1,79 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airpro4;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_AMBIENT_SOUND_CONTROL;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_ANC_MODE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_EQUALIZER_PRESET;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_TRANSPARENCY_MODE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer.TenBandEqualizerPresets;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarFunAirPro4SettingsCustomizer extends EarFunSettingsCustomizer {
@Override
public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
super.onPreferenceChange(preference, handler);
String key = preference.getKey();
if (key == null) {
return;
}
switch (key) {
case PREF_EARFUN_AMBIENT_SOUND_CONTROL:
onPreferenceChangeAmbientSoundControl(handler);
break;
case PREF_EARFUN_EQUALIZER_PRESET:
onPreferenceChangeEqualizerPreset(handler, Equalizer.TenBandEqualizer, TenBandEqualizerPresets);
break;
}
// if the band sliders match a preset, update the preset list
if (Equalizer.containsKey(Equalizer.TenBandEqualizer, key)) {
int equalizerPreset = getSelectedPresetFromEqualizerBands(handler,
Equalizer.TenBandEqualizer, TenBandEqualizerPresets);
ListPreference listPreferenceEqualizerPreset = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
if (listPreferenceEqualizerPreset != null) {
listPreferenceEqualizerPreset.setValue(Integer.toString(equalizerPreset));
}
}
}
@Override
public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
super.customizeSettings(handler, prefs, rootKey);
initializeEqualizerPresetListPreference(handler, TenBandEqualizerPresets);
}
private void onPreferenceChangeAmbientSoundControl(DeviceSpecificSettingsHandler handler) {
ListPreference listPreferenceAmbientSound = handler.findPreference(PREF_EARFUN_AMBIENT_SOUND_CONTROL);
ListPreference listPreferenceTransparencyMode = handler.findPreference(PREF_EARFUN_TRANSPARENCY_MODE);
ListPreference listPreferenceAncMode = handler.findPreference(PREF_EARFUN_ANC_MODE);
if (listPreferenceAmbientSound == null || listPreferenceTransparencyMode == null || listPreferenceAncMode == null) {
return;
}
switch (listPreferenceAmbientSound.getValue()) {
case "1": // noise cancelling
listPreferenceTransparencyMode.setVisible(false);
listPreferenceAncMode.setVisible(true);
break;
case "2": // transparency
listPreferenceTransparencyMode.setVisible(true);
listPreferenceAncMode.setVisible(false);
break;
default:
listPreferenceTransparencyMode.setVisible(false);
listPreferenceAncMode.setVisible(false);
}
}
public EarFunAirPro4SettingsCustomizer(final GBDevice device) {
super(device);
}
}

View File

@ -3,8 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunPacketEncoder;
@ -15,18 +13,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarFunAirSProtocol extends EarFunProtocol {
private static final Logger LOG = LoggerFactory.getLogger(EarFunAirSProtocol.class);
public byte[] encodeSendConfigurationCustomizer(String config) {
if (containsKey(Equalizer.SixBandEqualizer, config)) {
@Override
public byte[] encodeSendConfiguration(String config) {
if (Equalizer.containsKey(Equalizer.SixBandEqualizer, config)) {
Prefs prefs = getDevicePrefs();
return EarFunPacketEncoder.encodeSetEqualizerSixBands(prefs);
}
return null;
}
private static boolean containsKey(Equalizer.BandConfig[] array, String key) {
return Arrays.stream(array)
.anyMatch(element -> element.key.equals(key));
return super.encodeSendConfiguration(config);
}
protected EarFunAirSProtocol(GBDevice device) {

View File

@ -0,0 +1,54 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.airs;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_EQUALIZER_PRESET;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer.SixBandEqualizerPresets;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarFunAirSSettingsCustomizer extends EarFunSettingsCustomizer {
private static final Logger LOG = LoggerFactory.getLogger(EarFunAirSSettingsCustomizer.class);
@Override
public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
super.onPreferenceChange(preference, handler);
String key = preference.getKey();
if (key == null) {
return;
}
switch (key) {
case PREF_EARFUN_EQUALIZER_PRESET:
onPreferenceChangeEqualizerPreset(handler,
Equalizer.SixBandEqualizer, SixBandEqualizerPresets);
break;
}
// if the band sliders match a preset, update the preset list
if (Equalizer.containsKey(Equalizer.SixBandEqualizer, key)) {
int equalizerPreset = getSelectedPresetFromEqualizerBands(handler,
Equalizer.SixBandEqualizer, SixBandEqualizerPresets);
ListPreference listPreferenceEqualizerPreset = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
if (listPreferenceEqualizerPreset != null) {
listPreferenceEqualizerPreset.setValue(Integer.toString(equalizerPreset));
}
}
}
@Override
public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
super.customizeSettings(handler, prefs, rootKey);
initializeEqualizerPresetListPreference(handler, SixBandEqualizerPresets);
}
public EarFunAirSSettingsCustomizer(final GBDevice device) {
super(device);
}
}

View File

@ -26,4 +26,5 @@ public class EarFunSettingsPreferenceConst {
public static final String PREF_EARFUN_EQUALIZER_BAND_8000 = "pref_earfun_equalizer_band_8000";
public static final String PREF_EARFUN_EQUALIZER_BAND_15000 = "pref_earfun_equalizer_band_15000";
public static final String PREF_EARFUN_EQUALIZER_BAND_16000 = "pref_earfun_equalizer_band_16000";
public static final String PREF_EARFUN_EQUALIZER_PRESET = "pref_earfun_equalizer_preset";
}

View File

@ -2,6 +2,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.*;
import java.util.Arrays;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.R;
public class Equalizer {
public enum Band {
SIX_BAND_63((byte) 0xA1, (short) 0x00BD, (short) 0x0B33),
@ -42,6 +47,22 @@ public class Equalizer {
this.qFactor = qFactor;
this.defaultGain = defaultGain;
}
public byte getBandId() {
return bandId;
}
public short getFrequency() {
return frequency;
}
public short getqFactor() {
return qFactor;
}
public double getDefaultGain() {
return defaultGain;
}
}
public static class BandConfig {
@ -50,6 +71,14 @@ public class Equalizer {
this.key = key;
}
public Band getBand() {
return band;
}
public String getKey() {
return key;
}
public Band band;
public String key;
}
@ -79,4 +108,150 @@ public class Equalizer {
new BandConfig(Band.TEN_BAND_8000, PREF_EARFUN_EQUALIZER_BAND_8000),
new BandConfig(Band.TEN_BAND_16000, PREF_EARFUN_EQUALIZER_BAND_16000),
};
public interface EqualizerPreset {
String getPresetName();
int getLocalizedPresetName();
double[] getSettings();
default String getFormattedSettings() {
return Arrays.toString(getSettings());
}
static void printAllPresets(EqualizerPreset[] presets) {
for (EqualizerPreset preset : presets) {
System.out.println(preset.getPresetName() + ": " + preset.getFormattedSettings());
}
}
}
public enum SixBandPreset implements EqualizerPreset {
// Default: Keeps all bands at their default values
DEFAULT(R.string.pref_title_equalizer_normal, new double[]{0, 0, 0, 0, 0, 0}),
// Natural: Balanced and natural sound profile that reproduces audio without any coloration
NATURAL(R.string.pref_title_equalizer_natural, new double[]{0, 0, 1, 1, 2, 3}),
// Bass Boost: Emphasizes the low frequencies for a deep, powerful bass
BASS_BOOST(R.string.pref_title_equalizer_bass_boost, new double[]{8, 3, 2, 0, 0, 0}),
// Treble Boost: Enhances the high frequencies for a crisp and bright sound
TREBLE_BOOST(R.string.pref_title_equalizer_trebble, new double[]{0, 0, 0, 2, 3, 5}),
// Soft: Creates a gentle, smooth, and mellow sound
SOFT(R.string.pref_title_equalizer_soft, new double[]{-5, -2, +2, +3, 0, -3}),
// Dynamic: Produces a lively and energetic sound with well-defined bass and crisp highs
DYNAMIC(R.string.pref_title_equalizer_dynamic, new double[]{+7, +3, +2, +3, +5, +7}),
// Clear: Achieves a balanced and transparent sound, ideal for detailed audio work
CLEAR(R.string.pref_title_equalizer_clear, new double[]{+2, 0, +3, +5, +3, +5}),
// Relaxed: Produces a calming and soothing sound, perfect for unwinding
RELAXED(R.string.sony_equalizer_preset_relaxed, new double[]{+2, +1, 0, -1, -3, -5}),
// Vocal: Enhances the mid-range frequencies for clear and prominent vocals
VOCAL(R.string.sony_equalizer_preset_vocal, new double[]{-2, 0, +4, +5, +2, -1});
public final String presetName;
public final int localizedPresetName;
public final double[] settings;
SixBandPreset(String name, double[] settings) {
this.presetName = name;
this.localizedPresetName = -1;
this.settings = settings;
}
SixBandPreset(int localizedName, double[] settings) {
this.presetName = "";
this.localizedPresetName = localizedName;
this.settings = settings;
}
public String getPresetName() {
return presetName;
}
public int getLocalizedPresetName() {
return localizedPresetName;
}
public double[] getSettings() {
return settings;
}
}
public enum TenBandPreset implements EqualizerPreset {
// Default: Keeps all bands at their default values
DEFAULT(R.string.pref_title_equalizer_normal, new double[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
// Natural: Balanced and natural sound profile that reproduces audio without any coloration
NATURAL(R.string.pref_title_equalizer_natural, new double[]{0, 0, 1, 2, 2, -1, -1, -1, -2, 1}),
// Bass Boost: Emphasizes the low frequencies for a deep, powerful bass
BASS_BOOST(R.string.pref_title_equalizer_bass_boost, new double[]{+8, +6, +4, +2, 0, 0, 0, 0, 0, 0}),
// Treble Boost: Enhances the high frequencies for a crisp and bright sound
TREBLE_BOOST(R.string.pref_title_equalizer_trebble, new double[]{0, 0, 0, 0, 0, 0, +2, +2, +3, +4}),
// Soft: Creates a gentle, smooth, and mellow sound
SOFT(R.string.pref_title_equalizer_soft, new double[]{-5, -4, -2, 0, +2, +3, 0, -2, -3, -5}),
// Dynamic: Produces a lively and energetic sound with well-defined bass and crisp highs
DYNAMIC(R.string.pref_title_equalizer_dynamic, new double[]{+6, +6, +4, +2, +2, +3, +4, +5, +6, +7}),
// Clear: Achieves a balanced and transparent sound, ideal for detailed audio work
CLEAR(R.string.pref_title_equalizer_clear, new double[]{+3, +2, +2, +2, +3, +5, +3, +3, +4, +5}),
// Relaxed: Produces a calming and soothing sound, perfect for unwinding
RELAXED(R.string.sony_equalizer_preset_relaxed, new double[]{+2, +1, 0, 0, 0, -1, -2, -3, -4, -5}),
// Vocal: Enhances the mid-range frequencies for clear and prominent vocals
VOCAL(R.string.sony_equalizer_preset_vocal, new double[]{-3, -2, 0, +2, +3, +5, +3, +2, 0, -1});
public final String presetName;
public final int localizedPresetName;
public final double[] settings;
TenBandPreset(String name, double[] settings) {
this.presetName = name;
this.localizedPresetName = -1;
this.settings = settings;
}
TenBandPreset(int localizedName, double[] settings) {
this.presetName = "";
this.localizedPresetName = localizedName;
this.settings = settings;
}
public String getPresetName() {
return presetName;
}
public int getLocalizedPresetName() {
return localizedPresetName;
}
public double[] getSettings() {
return settings;
}
}
public static boolean containsKey(Equalizer.BandConfig[] array, String key) {
return Arrays.stream(array)
.anyMatch(element -> Objects.equals(element.key, key));
}
public static EqualizerPreset[] SixBandEqualizerPresets = {
SixBandPreset.DEFAULT,
SixBandPreset.NATURAL,
SixBandPreset.BASS_BOOST,
SixBandPreset.TREBLE_BOOST,
SixBandPreset.SOFT,
SixBandPreset.DYNAMIC,
SixBandPreset.CLEAR,
SixBandPreset.RELAXED,
SixBandPreset.VOCAL
};
public static EqualizerPreset[] TenBandEqualizerPresets = {
TenBandPreset.DEFAULT,
TenBandPreset.NATURAL,
TenBandPreset.BASS_BOOST,
TenBandPreset.TREBLE_BOOST,
TenBandPreset.SOFT,
TenBandPreset.DYNAMIC,
TenBandPreset.CLEAR,
TenBandPreset.RELAXED,
TenBandPreset.VOCAL
};
}

View File

@ -3548,7 +3548,7 @@
<string-array name="earfun_transparency_mode_names">
<item>@string/pref_default</item>
<item>@string/earfun_transparency_mode_natural</item>
<item>@string/pref_title_equalizer_natural</item>
</string-array>
<string-array name="earfun_transparency_mode_values">

View File

@ -2638,6 +2638,7 @@
<string name="pref_title_equalizer_dynamic">Dynamic</string>
<string name="pref_title_equalizer_clear">Clear</string>
<string name="pref_title_equalizer_trebble">Treble boost</string>
<string name="pref_title_equalizer_natural">Natural</string>
<string name="prefs_dolby_mode">Dolby Mode</string>
<string name="prefs_equalizer">Equalizer</string>
<string name="prefs_equalizer_summary">Enable or disable equalizer</string>
@ -3706,7 +3707,6 @@
<string name="gree_pair_status_failure">Pairing failed: %s</string>
<string name="gree_pair_clipboard">Device name: %s\nMac address: %s\nBind key: %s</string>
<string name="earfun_transparency_mode">Transparency mode</string>
<string name="earfun_transparency_mode_natural">Natural</string>
<string name="earfun_anc_mode">ANC mode</string>
<string name="earfun_anc_mode_strong">Strong ANC</string>
<string name="earfun_anc_mode_balanced">Balanced ANC</string>

View File

@ -7,87 +7,91 @@
android:key="pref_earfun_10_band_equalizer"
android:persistent="false"
android:title="@string/pref_header_equalizer">
<ListPreference
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_preset"
android:summary="%s"
android:title="@string/prefs_equalizer_preset" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_31.5"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="31.5 Hz" />
android:max="10"
android:title="31.5 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_63"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="63 Hz" />
android:max="10"
android:title="63 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_125"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="125 Hz" />
android:max="10"
android:title="125 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_250"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="250 Hz" />
android:max="10"
android:title="250 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_500"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="500 Hz" />
android:max="10"
android:title="500 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_1000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="1 kHz" />
android:max="10"
android:title="1 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_2000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="2 kHz" />
android:max="10"
android:title="2 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_4000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="4 kHz" />
android:max="10"
android:title="4 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_8000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="8 kHz" />
android:max="10"
android:title="8 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_16000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="16 kHz" />
android:max="10"
android:title="16 kHz"
app:min="-10"
app:showSeekBarValue="true" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -7,55 +7,59 @@
android:key="pref_earfun_6_band_equalizer"
android:persistent="false"
android:title="@string/pref_header_equalizer">
<ListPreference
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_preset"
android:summary="%s"
android:title="@string/prefs_equalizer_preset" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_63"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="63 Hz" />
android:max="10"
android:title="63 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_180"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="180 Hz" />
android:max="10"
android:title="180 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_500"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="500 Hz" />
android:max="10"
android:title="500 Hz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_1000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="1 kHz" />
android:max="10"
android:title="1 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_8000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="8 kHz" />
android:max="10"
android:title="8 kHz"
app:min="-10"
app:showSeekBarValue="true" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_earfun_equalizer_band_15000"
android:max="6"
app:min="-6"
app:showSeekBarValue="true"
android:title="15 kHz" />
android:max="10"
android:title="15 kHz"
app:min="-10"
app:showSeekBarValue="true" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -3,8 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
android:icon="@drawable/ic_bluetooth"
android:inputType="text"
android:key="pref_earfun_device_name"
android:title="@string/prefs_device_name"
android:inputType="text"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>