1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-25 01:55:50 +01:00

Add support for Redmi Buds 5 Pro (#4343)

This PR introduces support for the Redmi Buds 5 Pro earbuds.

Working:

- Connection and authentication;
- Firmware version;
- Battery percentage;
- Switching between ANC/Transparency/OFF, their relative strength settings and Adaptive ANC toggle;
- Configuring all touch options;
- Ear detection play/pause, Auto call answer, Double connection, Adaptive sound settings toggles;
- Equalizer presets and custom curve;

Not working:

- Personalized ANC (code for toggle is present but commented-out, as the logic for ear calibration is missing);
- Spatial Audio
- Find my earbuds
- Firmware Update

Closes #3566

Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/4343
Co-authored-by: Jonathan Gobbo <jonathangobbo@outlook.it>
Co-committed-by: Jonathan Gobbo <jonathangobbo@outlook.it>
This commit is contained in:
Jonathan Gobbo 2024-11-28 18:16:17 +00:00 committed by José Rebelo
parent 9270bd9003
commit 287a759e28
20 changed files with 1978 additions and 0 deletions

View File

@ -365,6 +365,39 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_GALAXY_BUDS_PRO_ANC_LEVEL="pref_galaxy_buds_pro_anc_level";
public static final String PREFS_GALAXY_BUDS_SEAMLESS_CONNECTION="prefs_galaxy_buds_seamless_connection";
public static final String PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL="pref_redmi_buds_5_pro_ambient_sound_control";
public static final String PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH="pref_redmi_buds_5_pro_noise_cancelling_strength";
public static final String PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH="pref_redmi_buds_5_pro_transparency_strength";
public static final String PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING="pref_redmi_buds_5_pro_adaptive_noise_cancelling";
// public static final String PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING="pref_redmi_buds_5_pro_personalized_noise_cancelling";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT="pref_redmi_buds_5_pro_control_single_tap_left";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_single_tap_right";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT="pref_redmi_buds_5_pro_control_double_tap_left";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_double_tap_right";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT="pref_redmi_buds_5_pro_control_triple_tap_left";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT="pref_redmi_buds_5_pro_control_triple_tap_right";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT ="pref_redmi_buds_5_pro_control_long_tap_mode_left";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT ="pref_redmi_buds_5_pro_control_long_tap_mode_right";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT ="pref_redmi_buds_5_pro_control_long_tap_settings_left";
public static final String PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT ="pref_redmi_buds_5_pro_control_long_tap_settings_right";
public static final String PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION="pref_redmi_buds_5_pro_wearing_detection";
public static final String PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL="pref_redmi_buds_5_pro_auto_reply_phonecall";
public static final String PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION="pref_redmi_buds_5_pro_double_connection";
// public static final String PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND="pref_redmi_buds_5_pro_surround_sound";
// public static final String PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND_MODE="pref_redmi_buds_5_pro_surround_sound_mode";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET="pref_redmi_buds_5_pro_equalizer_preset";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62="pref_redmi_buds_5_pro_equalizer_band_62";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125="pref_redmi_buds_5_pro_equalizer_band_125";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250="pref_redmi_buds_5_pro_equalizer_band_250";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500="pref_redmi_buds_5_pro_equalizer_band_500";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k="pref_redmi_buds_5_pro_equalizer_band_1k";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k="pref_redmi_buds_5_pro_equalizer_band_2k";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k="pref_redmi_buds_5_pro_equalizer_band_4k";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k="pref_redmi_buds_5_pro_equalizer_band_8k";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k="pref_redmi_buds_5_pro_equalizer_band_12k";
public static final String PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k="pref_redmi_buds_5_pro_equalizer_band_16k";
public static final String PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND="pref_redmi_buds_5_pro_adaptive_sound";
public static final String PREF_SONY_AUDIO_CODEC = "pref_sony_audio_codec";
public static final String PREF_SONY_PROTOCOL_VERSION = "pref_protocol_version";
public static final String PREF_SONY_ACTUAL_PROTOCOL_VERSION = "pref_actual_protocol_version";

View File

@ -676,6 +676,39 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_WATCHFACE);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING);
// addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION);
// addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND);
// addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_SURROUND_SOUND_MODE);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k);
addPreferenceHandlerFor(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k);
addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL);
addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE);
addPreferenceHandlerFor(PREF_SONY_FOCUS_VOICE);

View File

@ -0,0 +1,103 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro;
import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
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.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.RedmiBuds5ProDeviceSupport;
public class RedmiBuds5ProCoordinator extends AbstractDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public String getManufacturer() {
return "Xiaomi";
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return RedmiBuds5ProDeviceSupport.class;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_redmi_buds_5_pro;
}
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Redmi Buds 5 Pro");
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}
@Override
public int getBatteryCount() {
return 3;
}
@Override
public BatteryConfig[] getBatteryConfig(GBDevice device) {
BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
return new BatteryConfig[]{battery1, battery2, battery3};
}
@Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_headphones);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_gestures);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_redmibuds5pro_sound);
return deviceSpecificSettings;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new RedmiBuds5ProSettingsCustomizer(device);
}
@Override
public int getDefaultIconResource() {
return R.drawable.ic_device_nothingear;
}
@Override
public int getDisabledIconResource() {
return R.drawable.ic_device_nothingear_disabled;
}
}

View File

@ -0,0 +1,161 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class RedmiBuds5ProSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
final GBDevice device;
public RedmiBuds5ProSettingsCustomizer(final GBDevice device) {
this.device = device;
}
@Override
public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
// Hide or Show ANC/Transparency settings according to the current ambient sound control mode
if (preference.getKey().equals(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL)) {
String mode = ((ListPreference) preference).getValue();
final Preference ancLevel = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH);
final Preference transparencyLevel = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH);
final Preference adaptiveAnc = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING);
// final Preference customizedAnc = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING);
if (ancLevel != null) {
ancLevel.setVisible(mode.equals("1"));
}
if (transparencyLevel != null) {
transparencyLevel.setVisible(mode.equals("2"));
}
if (adaptiveAnc != null) {
adaptiveAnc.setVisible(mode.equals("1"));
}
// if (customizedAnc != null) {
// customizedAnc.setVisible(mode.equals("1"));
// }
}
}
@Override
public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
final ListPreference longPressLeft = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT);
final ListPreference longPressRight = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT);
final Preference longPressLeftSettings = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT);
final Preference longPressRightSettings = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT);
final ListPreference equalizerPreset = handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET);
if (longPressLeft != null) {
final Preference.OnPreferenceChangeListener longPressLeftButtonListener = (preference, newVal) -> {
String mode = newVal.toString();
if (longPressLeftSettings != null) {
longPressLeftSettings.setVisible(mode.equals("6"));
}
return true;
};
longPressLeftButtonListener.onPreferenceChange(longPressLeft, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, "6"));
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, longPressLeftButtonListener);
}
if (longPressRight != null) {
final Preference.OnPreferenceChangeListener longPressRightButtonListener = (preference, newVal) -> {
String mode = newVal.toString();
if (longPressRightSettings != null) {
longPressRightSettings.setVisible(mode.equals("6"));
}
return true;
};
longPressRightButtonListener.onPreferenceChange(longPressRight, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, "6"));
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, longPressRightButtonListener);
}
if (equalizerPreset != null) {
final Preference.OnPreferenceChangeListener equalizerPresetListener = (preference, newVal) -> {
final List<Preference> prefsToDisable = Arrays.asList(
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k),
handler.findPreference(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k)
);
String mode = newVal.toString();
for (Preference pref : prefsToDisable) {
if (pref != null) {
pref.setEnabled(mode.equals("10"));
}
}
return true;
};
equalizerPresetListener.onPreferenceChange(equalizerPreset, prefs.getString(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, "0"));
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, equalizerPresetListener);
}
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
parcel.writeParcelable(device, 0);
}
public static final Creator<RedmiBuds5ProSettingsCustomizer> CREATOR = new Creator<RedmiBuds5ProSettingsCustomizer>() {
@Override
public RedmiBuds5ProSettingsCustomizer createFromParcel(final Parcel in) {
final GBDevice device = in.readParcelable(RedmiBuds5ProSettingsCustomizer.class.getClassLoader());
return new RedmiBuds5ProSettingsCustomizer(device);
}
@Override
public RedmiBuds5ProSettingsCustomizer[] newArray(final int size) {
return new RedmiBuds5ProSettingsCustomizer[size];
}
};
}

View File

@ -0,0 +1,47 @@
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs;
public class Configuration {
public enum Config {
GESTURES((byte) 0x02),
AUTO_ANSWER((byte) 0x03),
DOUBLE_CONNECTION((byte) 0x04),
EAR_DETECTION((byte) 0x06),
EQ_PRESET((byte) 0x07),
LONG_GESTURES((byte) 0x0a),
EFFECT_STRENGTH((byte) 0x0b),
ADAPTIVE_ANC((byte) 0x25),
ADAPTIVE_SOUND((byte) 0x29),
EQ_CURVE((byte) 0x37),
CUSTOMIZED_ANC((byte) 0x3b),
UNKNOWN((byte) 0xff);
public final byte value;
Config(byte value) {
this.value = value;
}
public static Config fromCode(final byte code) {
for (final Config config : values()) {
if (config.value == code) {
return config;
}
}
return Config.UNKNOWN;
}
}
public enum StrengthTarget {
ANC((byte) 0x01),
TRANSPARENCY((byte) 0x02);
public final byte value;
StrengthTarget(byte value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,22 @@
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs;
public class Gestures {
public enum InteractionType {
SINGLE((byte) 0x04),
DOUBLE((byte) 0x01),
TRIPLE((byte) 0x02),
LONG((byte) 0x03);
public final byte value;
InteractionType(byte value) {
this.value = value;
}
}
public enum Position {
LEFT,
RIGHT
}
}

View File

@ -274,6 +274,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8pro.MiBand8Pro
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband9.MiBand9Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatch.MiWatchLiteCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miwatchcolorsport.MiWatchColorSportCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.RedmiBuds5ProCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartband2.RedmiSmartBand2Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartbandpro.RedmiSmartBandProCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2.RedmiWatch2Coordinator;
@ -346,6 +347,7 @@ public enum DeviceType {
MIBAND9(MiBand9Coordinator.class),
MIWATCHLITE(MiWatchLiteCoordinator.class),
MIWATCHCOLORSPORT(MiWatchColorSportCoordinator.class),
REDMIBUDS5PRO(RedmiBuds5ProCoordinator.class),
REDMIWATCH3ACTIVE(RedmiWatch3ActiveCoordinator.class),
REDMIWATCH3(RedmiWatch3Coordinator.class),
REDMISMARTBAND2(RedmiSmartBand2Coordinator.class),

View File

@ -0,0 +1,46 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class RedmiBuds5ProDeviceSupport extends AbstractSerialDeviceSupport {
@Override
protected GBDeviceProtocol createDeviceProtocol() {
return new RedmiBuds5ProProtocol(getDevice());
}
@Override
protected GBDeviceIoThread createDeviceIOThread() {
return new RedmiBuds5ProIOThread(getDevice(), getContext(),
(RedmiBuds5ProProtocol) getDeviceProtocol(),
RedmiBuds5ProDeviceSupport.this, getBluetoothAdapter());
}
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
}
}

View File

@ -0,0 +1,66 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.ParcelUuid;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
public class RedmiBuds5ProIOThread extends BtClassicIoThread {
private static final Logger LOG = LoggerFactory.getLogger(RedmiBuds5ProIOThread.class);
private final RedmiBuds5ProProtocol redmiProtocol;
public RedmiBuds5ProIOThread(GBDevice gbDevice, Context context, RedmiBuds5ProProtocol redmiProtocol, RedmiBuds5ProDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
super(gbDevice, context, redmiProtocol, deviceSupport, btAdapter);
this.redmiProtocol = redmiProtocol;
}
@Override
protected byte[] parseIncoming(InputStream stream) throws IOException {
byte[] buffer = new byte[1048576];
int bytes = stream.read(buffer);
LOG.debug("read {} bytes. {}", bytes, hexdump(buffer, 0, bytes));
return Arrays.copyOf(buffer, bytes);
}
@NonNull
@Override
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
return this.redmiProtocol.UUID_DEVICE_CTRL;
}
@Override
protected void initialize() {
write(redmiProtocol.encodeStartAuthentication());
setUpdateState(GBDevice.State.INITIALIZING);
}
}

View File

@ -0,0 +1,548 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.Config;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.StrengthTarget;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.InteractionType;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.Position;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Authentication;
import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Message;
import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.MessageType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Opcode;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class RedmiBuds5ProProtocol extends GBDeviceProtocol {
private static final Logger LOG = LoggerFactory.getLogger(RedmiBuds5ProProtocol.class);
final UUID UUID_DEVICE_CTRL = UUID.fromString("0000fd2d-0000-1000-8000-00805f9b34fb");
private byte sequenceNumber = 0;
protected RedmiBuds5ProProtocol(GBDevice device) {
super(device);
}
public byte[] encodeStartAuthentication() {
byte[] authRnd = Authentication.getRandomChallenge();
LOG.debug("[AUTH] Sending challenge: {}", hexdump(authRnd));
byte[] payload = new byte[17];
payload[0] = 0x01;
System.arraycopy(authRnd, 0, payload, 1, 16);
return new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CHALLENGE, sequenceNumber++, payload).encode();
}
@Override
public byte[] encodeSendConfiguration(String config) {
switch (config) {
case PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL:
return encodeSetAmbientSoundControl();
case PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH:
return encodeSetEffectStrength(config, StrengthTarget.ANC);
case PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH:
return encodeSetEffectStrength(config, StrengthTarget.TRANSPARENCY);
case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING:
return encodeSetBooleanConfig(config, Config.ADAPTIVE_ANC);
// case PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING:
// return encodeSetBooleanConfig(config, Config.CUSTOMIZED_ANC);
case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT:
return encodeSetGesture(config, InteractionType.SINGLE, Position.LEFT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT:
return encodeSetGesture(config, InteractionType.SINGLE, Position.RIGHT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT:
return encodeSetGesture(config, InteractionType.DOUBLE, Position.LEFT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT:
return encodeSetGesture(config, InteractionType.DOUBLE, Position.RIGHT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT:
return encodeSetGesture(config, InteractionType.TRIPLE, Position.LEFT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT:
return encodeSetGesture(config, InteractionType.TRIPLE, Position.RIGHT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT:
return encodeSetGesture(config, InteractionType.LONG, Position.LEFT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT:
return encodeSetGesture(config, InteractionType.LONG, Position.RIGHT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT:
return encodeSetLongGestureMode(config, Position.LEFT);
case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT:
return encodeSetLongGestureMode(config, Position.RIGHT);
case PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION:
return encodeSetEarDetection();
case PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL:
return encodeSetBooleanConfig(config, Config.AUTO_ANSWER);
case PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION:
return encodeSetBooleanConfig(config, Config.DOUBLE_CONNECTION);
case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND:
return encodeSetBooleanConfig(config, Config.ADAPTIVE_SOUND);
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET:
return encodeSetIntegerConfig(config, Config.EQ_PRESET);
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k:
case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k:
return encodeSetCustomEqualizer();
default:
LOG.debug("Unsupported config: {}", config);
}
return super.encodeSendConfiguration(config);
}
public byte[] encodeSetCustomEqualizer() {
Prefs prefs = getDevicePrefs();
List<String> bands = List.of(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125,
PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k,
PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k,
PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k);
byte[] eqCurve = new byte[10];
for (int i = 0; i < 10; i++) {
eqCurve[i] = (byte) Integer.parseInt(prefs.getString(bands.get(i), "0"));
}
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{
0x24, 0x00, 0x37, 0x05, 0x01, 0x01, 0x0A,
0x00, 0x3E, eqCurve[0], 0x00, 0x7D, eqCurve[1],
0x00, (byte) 0xFA, eqCurve[2], 0x01, (byte) 0xF4, eqCurve[3],
0x03, (byte) 0xE8, eqCurve[4], 0x07, (byte) 0xE0, eqCurve[5],
0x0F, (byte) 0xA0, eqCurve[6], 0x1F, 0x40, eqCurve[7],
0x2E, (byte) 0xE0, eqCurve[8], 0x3E, (byte) 0x80, eqCurve[9]
}).encode();
}
public byte[] encodeSetEarDetection() {
Prefs prefs = getDevicePrefs();
byte value = (byte) (prefs.getBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, false) ? 0x00 : 0x01);
return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x06, value}).encode();
}
public byte[] encodeSetLongGestureMode(String config, Position position) {
Prefs prefs = getDevicePrefs();
byte value = (byte) Integer.parseInt(prefs.getString(config, "7"));
byte[] payload = new byte[]{0x04, 0x00, 0x0a, (byte) 0xFF, (byte) 0xFF};
if (position == Position.LEFT) {
payload[3] = value;
} else {
payload[4] = value;
}
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode();
}
public byte[] encodeSetGesture(String config, InteractionType interactionType, Position position) {
Prefs prefs = getDevicePrefs();
byte value = (byte) Integer.parseInt(prefs.getString(config, "1"));
byte[] payload = new byte[]{0x05, 0x00, 0x02, interactionType.value, (byte) 0xFF, (byte) 0xFF};
if (position == Position.LEFT) {
payload[4] = value;
} else {
payload[5] = value;
}
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode();
}
public byte[] encodeSetEffectStrength(String pref, StrengthTarget effect) {
Prefs prefs = getDevicePrefs();
byte mode = (byte) Integer.parseInt(prefs.getString(pref, "0"));
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x04, 0x00, 0x0b, effect.value, mode}).encode();
}
public byte[] encodeSetIntegerConfig(String pref, Config config) {
Prefs prefs = getDevicePrefs();
byte value = (byte) Integer.parseInt(prefs.getString(pref, "0"));
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode();
}
public byte[] encodeSetBooleanConfig(String pref, Config config) {
Prefs prefs = getDevicePrefs();
byte value = (byte) (prefs.getBoolean(pref, false) ? 0x01 : 0x00);
return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode();
}
public byte[] encodeGetConfig() {
List<Config> configs = List.of(Config.EFFECT_STRENGTH, Config.ADAPTIVE_ANC, // Config.CUSTOMIZED_ANC,
Config.GESTURES, Config.LONG_GESTURES, Config.EAR_DETECTION, Config.DOUBLE_CONNECTION,
Config.AUTO_ANSWER, Config.ADAPTIVE_SOUND, Config.EQ_PRESET, Config.EQ_CURVE);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
for (Config config : configs) {
Message message = new Message(MessageType.PHONE_REQUEST, Opcode.GET_CONFIG, sequenceNumber++, new byte[]{0x00, config.value});
outputStream.write(message.encode());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return outputStream.toByteArray();
}
public byte[] encodeSetAmbientSoundControl() {
Prefs prefs = getDevicePrefs();
byte mode = (byte) Integer.parseInt(prefs.getString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, "0"));
return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x04, mode}).encode();
}
public void decodeGetConfig(byte[] configPayload) {
SharedPreferences preferences = getDevicePrefs().getPreferences();
Editor editor = preferences.edit();
Config config = Config.fromCode(configPayload[2]);
switch (config) {
case GESTURES:
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT, Integer.toString(configPayload[4]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT, Integer.toString(configPayload[5]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT, Integer.toString(configPayload[7]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT, Integer.toString(configPayload[8]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT, Integer.toString(configPayload[10]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT, Integer.toString(configPayload[11]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, Integer.toString(configPayload[13]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, Integer.toString(configPayload[14]));
break;
case AUTO_ANSWER:
editor.putBoolean(PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL, configPayload[3] == 0x01);
break;
case DOUBLE_CONNECTION:
editor.putBoolean(PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION, configPayload[3] == 0x01);
break;
case EQ_PRESET:
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, Integer.toString(configPayload[3]));
break;
case LONG_GESTURES:
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT, Integer.toString(configPayload[3]));
editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT, Integer.toString(configPayload[4]));
break;
case EFFECT_STRENGTH:
byte mode = configPayload[4];
if (configPayload[3] == StrengthTarget.ANC.value) {
editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode));
} else if (configPayload[3] == StrengthTarget.TRANSPARENCY.value) {
editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode));
}
break;
case ADAPTIVE_ANC:
editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING, configPayload[3] == 0x01);
break;
case ADAPTIVE_SOUND:
editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND, configPayload[3] == 0x01);
break;
case EQ_CURVE:
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, Integer.toString(configPayload[12] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125, Integer.toString(configPayload[15] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, Integer.toString(configPayload[18] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, Integer.toString(configPayload[21] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k, Integer.toString(configPayload[24] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, Integer.toString(configPayload[27] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, Integer.toString(configPayload[30] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k, Integer.toString(configPayload[33] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, Integer.toString(configPayload[36] & 0xFF));
editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k, Integer.toString(configPayload[39] & 0xFF));
break;
// case CUSTOMIZED_ANC:
// editor.putBoolean(PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING, configPayload[3] == 0x01);
// break;
default:
LOG.debug("Unhandled device update: {}", hexdump(configPayload));
}
editor.apply();
}
private GBDeviceEventBatteryInfo parseBatteryInfo(byte batteryInfo, int index) {
if (batteryInfo == (byte) 0xff) {
return null;
}
GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
batteryEvent.state = (batteryInfo & 128) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
batteryEvent.batteryIndex = index;
batteryEvent.level = (batteryInfo & 127);
LOG.debug("Battery {}: {}", index, batteryEvent.level);
return batteryEvent;
}
private GBDeviceEvent[] decodeDeviceInfo(byte[] deviceInfoPayload) {
List<GBDeviceEvent> events = new ArrayList<>();
GBDeviceEventVersionInfo info = new GBDeviceEventVersionInfo();
byte[] fw = new byte[4];
byte[] vidPid = new byte[4];
byte[] batteryData = new byte[3];
int i = 0;
while (i < deviceInfoPayload.length) {
byte len = deviceInfoPayload[i];
byte index = deviceInfoPayload[i + 1];
switch (index) {
case 0x01:
System.arraycopy(deviceInfoPayload, i + 2, fw, 0, 4);
break;
case 0x03:
System.arraycopy(deviceInfoPayload, i + 2, vidPid, 0, 4);
break;
case 0x07:
System.arraycopy(deviceInfoPayload, i + 2, batteryData, 0, 3);
break;
}
i += len + 1;
}
String fwVersion1 = ((fw[0] >> 4) & 0xF) + "." + (fw[0] & 0xF) + "." + ((fw[1] >> 4) & 0xF) + "." + (fw[1] & 0xF);
String fwVersion2 = ((fw[2] >> 4) & 0xF) + "." + (fw[2] & 0xF) + "." + ((fw[3] >> 4) & 0xF) + "." + (fw[3] & 0xF);
String hwVersion = String.format("VID: 0x%02X%02X, PID: 0x%02X%02X", vidPid[0], vidPid[1], vidPid[2], vidPid[3]);
info.fwVersion = fwVersion1;
info.fwVersion2 = fwVersion2;
info.hwVersion = hwVersion;
events.add(parseBatteryInfo(batteryData[0], 1));
events.add(parseBatteryInfo(batteryData[1], 2));
events.add(parseBatteryInfo(batteryData[2], 0));
events.add(info);
return events.toArray(new GBDeviceEvent[0]);
}
private void decodeDeviceRunInfo(byte[] deviceRunInfoPayload) {
int i = 0;
while (i < deviceRunInfoPayload.length) {
byte len = deviceRunInfoPayload[i];
byte index = deviceRunInfoPayload[i + 1];
SharedPreferences preferences = getDevicePrefs().getPreferences();
Editor editor = preferences.edit();
switch (index) {
case 0x09:
byte mode = deviceRunInfoPayload[i + 2];
editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode));
break;
case 0x0A:
editor.putBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, deviceRunInfoPayload[i + 2] == 0x00);
}
editor.apply();
i += len + 1;
}
}
private GBDeviceEvent[] decodeDeviceUpdate(Message updateMessage) {
byte[] updatePayload = updateMessage.getPayload();
List<GBDeviceEvent> events = new ArrayList<>();
int i = 0;
while (i < updatePayload.length) {
byte len = updatePayload[i];
byte index = updatePayload[i + 1];
switch (index) {
case 0x00:
events.add(parseBatteryInfo(updatePayload[i + 2], 1));
events.add(parseBatteryInfo(updatePayload[i + 3], 2));
events.add(parseBatteryInfo(updatePayload[i + 4], 0));
break;
case 0x04:
SharedPreferences preferences = getDevicePrefs().getPreferences();
Editor editor = preferences.edit();
byte mode = updatePayload[i + 2];
editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode));
editor.apply();
break;
default:
LOG.debug("Unimplemented device update: {}", hexdump(updatePayload));
}
i += len + 1;
}
events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.REPORT_STATUS, updateMessage.getSequenceNumber(), new byte[]{}).encode()));
return events.toArray(new GBDeviceEvent[0]);
}
private GBDeviceEvent[] decodeNotifyConfig(Message notifyMessage) {
byte[] notifyPayload = notifyMessage.getPayload();
List<GBDeviceEvent> events = new ArrayList<>();
int i = 0;
while (i < notifyPayload.length) {
byte len = notifyPayload[i];
byte index = notifyPayload[i + 2];
switch (index) {
case 0x0C:
LOG.debug("Received earbuds position info");
/*
e.g. 0C 03
0011
wearing left, wearing right, left in case, right in case
*/
break;
case 0x0B:
SharedPreferences preferences = getDevicePrefs().getPreferences();
Editor editor = preferences.edit();
byte soundCtrlMode = notifyPayload[i + 3];
editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(soundCtrlMode));
byte mode = notifyPayload[i + 4];
if (notifyPayload[i + 3] == 0x01) {
editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode));
} else {
editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode));
}
editor.apply();
break;
}
i += len + 1;
}
events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.NOTIFY_CONFIG, notifyMessage.getSequenceNumber(), new byte[]{}).encode()));
return events.toArray(new GBDeviceEvent[0]);
}
private GBDeviceEvent[] handleAuthentication(Message authMessage) {
List<GBDeviceEvent> events = new ArrayList<>();
switch (authMessage.getOpcode()) {
case AUTH_CHALLENGE:
if (authMessage.getType() == MessageType.RESPONSE) {
LOG.debug("[AUTH] Received Challenge Response");
/*
Should check if equal, but does not really matter
*/
LOG.debug("[AUTH] Sending authentication confirmation");
events.add(new GBDeviceEventSendBytes(new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CONFIRM, sequenceNumber++, new byte[]{0x01, 0x00}).encode()));
} else {
byte[] responsePayload = authMessage.getPayload();
byte[] challenge = new byte[16];
System.arraycopy(responsePayload, 1, challenge, 0, 16);
LOG.info("[AUTH] Received Challenge: {}", hexdump(challenge));
Authentication auth = new Authentication();
byte[] challengeResponse = auth.computeChallengeResponse(challenge);
LOG.info("[AUTH] Sending Challenge Response: {}", hexdump(challengeResponse));
byte[] payload = new byte[17];
payload[0] = 0x01;
System.arraycopy(challengeResponse, 0, payload, 1, 16);
Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CHALLENGE, authMessage.getSequenceNumber(), payload);
events.add(new GBDeviceEventSendBytes(res.encode()));
}
break;
case AUTH_CONFIRM:
if (authMessage.getType() == MessageType.RESPONSE) {
LOG.debug("[AUTH] Confirmed first authentication step");
} else {
LOG.debug("[AUTH] Received authentication confirmation");
Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CONFIRM, authMessage.getSequenceNumber(), new byte[]{0x01});
LOG.debug("[AUTH] Sending final authentication confirmation");
events.add(new GBDeviceEventSendBytes(res.encode()));
LOG.debug("[INIT] Sending device info request");
Message info = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
events.add(new GBDeviceEventSendBytes(info.encode()));
LOG.debug("[INIT] Sending device run info request");
Message runInfo = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_RUN_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
events.add(new GBDeviceEventSendBytes(runInfo.encode()));
LOG.debug("[INIT] Sending configuration request");
events.add(new GBDeviceEventSendBytes(encodeGetConfig()));
}
break;
}
return events.toArray(new GBDeviceEvent[0]);
}
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
LOG.debug("Incoming message: {}", hexdump(responseData));
List<GBDeviceEvent> events = new ArrayList<>();
List<Message> incomingMessages = Message.splitPiggybackedMessages(responseData);
for (Message message : incomingMessages) {
LOG.debug("Parsed message: {}", message);
switch (message.getOpcode()) {
case AUTH_CHALLENGE:
case AUTH_CONFIRM:
events.addAll(Arrays.asList(handleAuthentication(message)));
break;
case GET_DEVICE_INFO:
LOG.debug("[INIT] Received device info");
if (getDevice().getState() != State.INITIALIZED) {
events.addAll(Arrays.asList(decodeDeviceInfo(message.getPayload())));
LOG.debug("[INIT] Device Initialized");
events.add(new GBDeviceEventUpdateDeviceState(State.INITIALIZED));
}
break;
case GET_DEVICE_RUN_INFO:
LOG.debug("[INIT] Received device run info");
decodeDeviceRunInfo(message.getPayload());
break;
case REPORT_STATUS:
events.addAll(Arrays.asList(decodeDeviceUpdate(message)));
break;
case GET_CONFIG:
decodeGetConfig(message.getPayload());
break;
case NOTIFY_CONFIG:
events.addAll(Arrays.asList(decodeNotifyConfig(message)));
break;
default:
LOG.debug("Unhandled message: {}", message);
break;
}
}
return events.toArray(new GBDeviceEvent[0]);
}
}

View File

@ -0,0 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
class AuthData {
final static byte[] SEQ = { 0x11, 0x22, 0x33, 0x33, 0x22, 0x11, 0x11, 0x22, 0x33, 0x33, 0x22, 0x11, 0x11, 0x22, 0x33, 0x33 };
final static int[][] COEFFICIENTS = {
{2,1,1,1,4,2,1,1,2,2,4,2,4,4,16,8},
{2,1,1,1,4,2,1,1,1,1,2,1,2,2,8,4},
{1,1,4,2,2,2,4,2,16,8,4,4,2,1,1,1},
{1,1,4,2,1,1,2,1,8,4,2,2,2,1,1,1},
{16,8,2,2,4,2,4,4,1,1,4,2,1,1,2,1},
{8,4,1,1,2,1,2,2,1,1,4,2,1,1,2,1},
{2,2,4,2,4,4,16,8,2,1,1,1,4,2,1,1},
{1,1,2,1,2,2,8,4,2,1,1,1,4,2,1,1},
{4,2,4,4,16,8,2,2,1,1,2,1,1,1,4,2},
{2,1,2,2,8,4,1,1,1,1,2,1,1,1,4,2},
{4,4,16,8,1,1,2,1,4,2,1,1,4,2,2,2},
{2,2,8,4,1,1,2,1,4,2,1,1,2,1,1,1},
{1,1,2,1,1,1,4,2,4,4,16,8,2,2,4,2},
{1,1,2,1,1,1,4,2,2,2,8,4,1,1,2,1},
{4,2,1,1,2,1,1,1,4,2,2,2,16,8,4,4},
{4,2,1,1,2,1,1,1,2,1,1,1,8,4,2,2}
};
}

View File

@ -0,0 +1,181 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
/*
Authentication based on the custom Bluetooth version of the SAFER+ encryption algorithm with:
- 128 bit key size
- 8 rounds
*/
public class Authentication {
private final static int PATTERN = 0x9999;
private final static int BLOCK_SIZE = 16;
byte[][] biasMatrix;
byte[] expTab;
byte[] logTab;
public Authentication() {
generateBiasMatrix();
generateExpTab();
generateLogTab();
}
private void generateBiasMatrix() {
biasMatrix = new byte[16][];
for (int i = 0; i < 16; i++) {
byte[] biasVec = new byte[16];
for (int j = 0; j < 16; j++) {
int exponent = (17 * (i + 2) + (j + 1));
BigInteger base = BigInteger.valueOf(45);
BigInteger modExp = base.pow(base.pow(exponent).mod(BigInteger.valueOf(257)).intValue())
.mod(BigInteger.valueOf(257));
byte val = (byte) (modExp.intValue() == 256 ? 0 : modExp.intValue());
biasVec[j] = val;
}
biasMatrix[i] = biasVec;
}
}
private void generateExpTab() {
expTab = new byte[256];
for (int i = 0; i < 256; i++) {
BigInteger base = BigInteger.valueOf(45);
BigInteger exp = base.pow(i).mod(BigInteger.valueOf(257));
expTab[i] = (byte) (i == 128 ? 0 : exp.intValue());
}
}
private void generateLogTab() {
logTab = new byte[256];
for (int i = 0; i < 256; i++) {
if (i == 0) {
logTab[i] = (byte) 128;
} else {
BigInteger base = BigInteger.valueOf(45);
BigInteger modExp = base.pow(i).mod(BigInteger.valueOf(257));
if (modExp.intValue() != 256) {
logTab[modExp.intValue()] = (byte) i;
}
}
}
}
private byte[][] keySchedule(byte[] keyInit) {
keyInit[15] ^= 6;
byte[][] keys = new byte[17][];
keys[0] = keyInit;
List<Byte> register = new ArrayList<>();
for (byte b : keyInit) {
register.add(b);
}
byte xor = register.stream().reduce((byte) 0x0, (cSum, e) -> (byte) (cSum ^ e));
register.add(xor);
for (int keyIdx = 1; keyIdx < keys.length; keyIdx++) {
byte[] keyI = new byte[16];
for (int i = 0; i < 17; i++) {
byte rot = (byte) (((register.get(i) & 0xff) >>> 5) | ((register.get(i) & 0xff) << (8 - 5)));
register.set(i, rot);
}
for (int i = 0; i < 16; i++) {
keyI[i] = (byte) (register.get((keyIdx + i) % 17) + biasMatrix[keyIdx - 1][i]);
}
keys[keyIdx] = keyI;
}
return keys;
}
private byte[] encrypt(byte[] plaintext, byte[][] keys) {
byte[] ciphertext = plaintext.clone();
for (int round = 0; round < 8; round++) {
if (round == 2) {
for (int i = 0; i < BLOCK_SIZE; i++) {
if ((1 << i & PATTERN) != 0) {
ciphertext[i] ^= plaintext[i];
} else {
ciphertext[i] += plaintext[i];
}
}
}
for (int i = 0; i < BLOCK_SIZE; i++) {
if ((1 << i & PATTERN) != 0) {
ciphertext[i] ^= keys[round * 2][i];
} else {
ciphertext[i] += keys[round * 2][i];
}
}
for (int i = 0; i < BLOCK_SIZE; i++) {
if ((1 << i & PATTERN) != 0) {
ciphertext[i] = expTab[ciphertext[i] & 0xff];
} else {
ciphertext[i] = logTab[ciphertext[i] & 0xff];
}
}
for (int i = 0; i < BLOCK_SIZE; i++) {
if ((1 << i & PATTERN) != 0) {
ciphertext[i] = (byte) (keys[round * 2 + 1][i] + ciphertext[i]);
} else {
ciphertext[i] = (byte) (keys[round * 2 + 1][i] ^ ciphertext[i]);
}
}
byte[] ciphertextCopy = ciphertext.clone();
for (int i = 0; i < BLOCK_SIZE; i++) {
byte cSum = 0;
for (int j = 0; j < BLOCK_SIZE; j++) {
cSum += (byte) (AuthData.COEFFICIENTS[i][j] * ciphertextCopy[j]);
}
ciphertext[i] = cSum;
}
}
for (int i = 0; i < BLOCK_SIZE; i++) {
if ((1 << i & PATTERN) != 0) {
ciphertext[i] = (byte) (keys[16][i] ^ ciphertext[i]);
} else {
ciphertext[i] = (byte) (keys[16][i] + ciphertext[i]);
}
}
return ciphertext;
}
public static byte[] getRandomChallenge() {
byte[] res = new byte[BLOCK_SIZE];
SecureRandom rnd = new SecureRandom();
rnd.nextBytes(res);
return res;
}
public byte[] computeChallengeResponse(byte[] challenge) {
byte[][] keys = keySchedule(challenge);
return encrypt(AuthData.SEQ, keys);
}
}

View File

@ -0,0 +1,124 @@
/* Copyright (C) 2024 Jonathan Gobbo
This file is part of Gadgetbridge.
Gadgetbridge 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, or
(at your option) any later version.
Gadgetbridge 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/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Message {
private static final byte[] MESSAGE_HEADER = {(byte) 0xfe, (byte) 0xdc, (byte) 0xba};
private static final byte MESSAGE_TRAILER = (byte) 0xef;
private static final int MESSAGE_OFFSET = MESSAGE_HEADER.length;
private final MessageType type;
private final Opcode opcode;
private final byte sequenceNumber;
private final byte[] payload;
public Message(final MessageType type, final Opcode opcode, final byte sequenceNumber, final byte[] payload) {
this.type = type;
this.opcode = opcode;
this.sequenceNumber = sequenceNumber;
this.payload = payload;
}
public static Message fromBytes(byte[] message) {
MessageType type = MessageType.fromCode(message[MESSAGE_OFFSET]);
Opcode opcode = Opcode.fromCode(message[MESSAGE_OFFSET + 1]);
int payloadOffset = MESSAGE_OFFSET + ((!type.isRequest()) ? 6 : 5);
byte sequenceNumber = message[payloadOffset - 1];
int actualPayloadLength = message.length - payloadOffset - 1;
byte[] payload = new byte[actualPayloadLength];
System.arraycopy(message, payloadOffset, payload, 0, actualPayloadLength);
return new Message(type, opcode, sequenceNumber, payload);
}
public static List<Message> splitPiggybackedMessages(byte[] input) {
List<Message> messages = new ArrayList<>();
List<Integer> startHeader = new ArrayList<>();
for (int i = 0; i < input.length - 3; i++) {
if (input[i] == MESSAGE_HEADER[0] && input[i + 1] == MESSAGE_HEADER[1] && input[i + 2] == MESSAGE_HEADER[2]) {
startHeader.add(i);
}
}
for (int i = 0; i < startHeader.size(); i++) {
if (i == startHeader.size() - 1) {
messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), input.length)));
} else {
messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), startHeader.get(i + 1))));
}
}
return messages;
}
public byte[] encode() {
int size = (!type.isRequest()) ? 2 : 1;
final ByteBuffer buf = ByteBuffer.allocate(payload.length + 8 + size);
int payloadLength = payload.length + size;
buf.order(ByteOrder.BIG_ENDIAN);
buf.put(MESSAGE_HEADER);
buf.put(type.getCode());
buf.put(opcode.getOpcode());
buf.putShort((short) payloadLength);
if (!type.isRequest()) {
buf.put((byte) 0x00);
}
buf.put(sequenceNumber);
buf.put(payload);
buf.put(MESSAGE_TRAILER);
return buf.array();
}
@NonNull
@Override
public String toString() {
return "Message{" + "type=" + type + ", opcode=" + opcode + ", sequenceNumber=" + sequenceNumber + ", payload=" + hexdump(payload) + '}';
}
public MessageType getType() {
return type;
}
public Opcode getOpcode() {
return opcode;
}
public byte getSequenceNumber() {
return sequenceNumber;
}
public byte[] getPayload() {
return payload;
}
}

View File

@ -0,0 +1,35 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
public enum MessageType {
PHONE_REQUEST(0xC4),
RESPONSE(0x04),
EARBUDS_REQUEST(0xC0),
EARBUDS_NOTIFY(0xC7),
UNKNOWN(0xFF);
private final byte code;
private final boolean isRequest;
MessageType(final int code) {
this.code = (byte) code;
this.isRequest = (this.code & 0x40) != 0;
}
public byte getCode() {
return this.code;
}
public boolean isRequest() {
return isRequest;
}
public static MessageType fromCode(final byte code) {
for (final MessageType messageType : values()) {
if (messageType.code == code) {
return messageType;
}
}
return MessageType.UNKNOWN;
}
}

View File

@ -0,0 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol;
public enum Opcode {
GET_DEVICE_INFO(0x02),
ANC(0x08),
GET_DEVICE_RUN_INFO(0x09),
REPORT_STATUS(0x0E),
AUTH_CHALLENGE(0x50),
AUTH_CONFIRM(0x51),
SET_CONFIG(0xF2),
GET_CONFIG(0xF3),
NOTIFY_CONFIG(0xF4),
UNKNOWN(0xFF);
private final byte opcode;
Opcode(final int opcode) {
this.opcode = (byte) opcode;
}
public byte getOpcode() {
return opcode;
}
public static Opcode fromCode(final byte code) {
for (final Opcode opcode : values()) {
if (opcode.opcode == code) {
return opcode;
}
}
return Opcode.UNKNOWN;
}
}

View File

@ -3377,6 +3377,160 @@
<item>3</item>
</string-array>
<string-array name="redmi_buds_5_pro_ambient_sound_control_names">
<item>@string/sony_ambient_sound_off</item>
<item>@string/sony_ambient_sound_noise_cancelling</item>
<item>@string/prefs_active_noise_cancelling_transparency</item>
</string-array>
<string-array name="redmi_buds_5_pro_ambient_sound_control_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="redmi_buds_5_pro_noise_cancelling_strength_names">
<item>@string/redmi_buds_5_pro_anc_balanced</item>
<item>@string/redmi_buds_5_pro_anc_light</item>
<item>@string/redmi_buds_5_pro_anc_deep</item>
</string-array>
<string-array name="redmi_buds_5_pro_noise_cancelling_strength_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="redmi_buds_5_pro_transparency_strength_names">
<item>@string/redmi_buds_5_pro_transparency_regular</item>
<item>@string/redmi_buds_5_pro_transparency_voice</item>
<item>@string/redmi_buds_5_pro_transparency_ambient</item>
</string-array>
<string-array name="redmi_buds_5_pro_transparency_strength_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="redmi_buds_5_pro_single_button_function_names">
<item>@string/none</item>
<item>@string/pref_media_playpause</item>
<item>@string/pref_media_previous</item>
<item>@string/pref_media_next</item>
<item>@string/pref_media_volumeup</item>
<item>@string/pref_media_volumedown</item>
</string-array>
<string-array name="redmi_buds_5_pro_single_button_function_values">
<item>8</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string-array name="redmi_buds_5_pro_button_function_names">
<item>@string/pref_media_playpause</item>
<item>@string/pref_media_previous</item>
<item>@string/pref_media_next</item>
<item>@string/pref_media_volumeup</item>
<item>@string/pref_media_volumedown</item>
</string-array>
<string-array name="redmi_buds_5_pro_button_function_values">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string-array name="redmi_buds_5_pro_long_button_mode_names">
<item>@string/pref_title_touch_voice_assistant</item>
<item>@string/pref_header_sony_ambient_sound_control</item>
</string-array>
<string-array name="redmi_buds_5_pro_long_button_mode_values">
<item>0</item>
<item>6</item>
</string-array>
<string-array name="redmi_buds_5_pro_long_button_settings_names">
<item>@string/redmi_buds_5_pro_combo_anc_off</item>
<item>@string/redmi_buds_5_pro_combo_transparency_off</item>
<item>@string/redmi_buds_5_pro_combo_anc_transparency</item>
<item>@string/redmi_buds_5_pro_combo_all</item>
</string-array>
<string-array name="redmi_buds_5_pro_long_button_settings_values">
<item>3</item>
<item>5</item>
<item>6</item>
<item>7</item>
</string-array>
<!-- <string-array name="redmi_buds_5_pro_surround_sound_mode_names">-->
<!-- <item>Standard</item>-->
<!-- <item>Music</item>-->
<!-- <item>Video</item>-->
<!-- </string-array>-->
<!-- <string-array name="redmi_buds_5_pro_surround_sound_mode_values">-->
<!-- <item>1</item>-->
<!-- <item>2</item>-->
<!-- <item>3</item>-->
<!-- </string-array>-->
<string-array name="redmi_buds_5_pro_equalizer_presets_names">
<item>@string/redmi_buds_5_pro_equalizer_preset_standard</item>
<item>@string/redmi_buds_5_pro_equalizer_preset_treble</item>
<item>@string/redmi_buds_5_pro_equalizer_preset_bass</item>
<item>@string/redmi_buds_5_pro_equalizer_preset_voice</item>
<item>@string/redmi_buds_5_pro_equalizer_preset_custom</item>
</string-array>
<string-array name="redmi_buds_5_pro_equalizer_presets_values">
<item>0</item>
<item>6</item>
<item>5</item>
<item>1</item>
<item>10</item>
</string-array>
<string-array name="redmi_buds_5_pro_equalizer_offsets_names">
<item>@string/redmi_buds_5_pro_equalizer_neg6</item>
<item>@string/redmi_buds_5_pro_equalizer_neg5</item>
<item>@string/redmi_buds_5_pro_equalizer_neg4</item>
<item>@string/redmi_buds_5_pro_equalizer_neg3</item>
<item>@string/redmi_buds_5_pro_equalizer_neg2</item>
<item>@string/redmi_buds_5_pro_equalizer_neg1</item>
<item>@string/redmi_buds_5_pro_equalizer_zero</item>
<item>@string/redmi_buds_5_pro_equalizer_1</item>
<item>@string/redmi_buds_5_pro_equalizer_2</item>
<item>@string/redmi_buds_5_pro_equalizer_3</item>
<item>@string/redmi_buds_5_pro_equalizer_4</item>
<item>@string/redmi_buds_5_pro_equalizer_5</item>
<item>@string/redmi_buds_5_pro_equalizer_6</item>
</string-array>
<string-array name="redmi_buds_5_pro_equalizer_offsets_values">
<item>134</item>
<item>133</item>
<item>132</item>
<item>131</item>
<item>130</item>
<item>129</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</string-array>
<string-array name="sony_ambient_sound_control_wind_noise_reduction_names">
<item>@string/sony_ambient_sound_off</item>
<item>@string/sony_ambient_sound_noise_cancelling</item>

View File

@ -1877,6 +1877,7 @@
<string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
<string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
<string name="devicetype_redmiwatch3">Redmi Watch 3</string>
<string name="devicetype_redmi_buds_5_pro">Redmi Buds 5 Pro</string>
<string name="devicetype_redmi_smart_band_2">Redmi Smart Band 2</string>
<string name="devicetype_redmi_watch_2">Redmi Watch 2</string>
<string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
@ -2611,6 +2612,51 @@
<string name="weight_kg">%1$.2f kg</string>
<string name="weight_lbs">%1$.2f lbs</string>
<string name="target">Target</string>
<string name="redmi_buds_5_pro_anc_balanced">Balanced</string>
<string name="redmi_buds_5_pro_anc_light">Light</string>
<string name="redmi_buds_5_pro_anc_deep">Deep</string>
<string name="redmi_buds_5_pro_transparency_strength">Transparency Strength</string>
<string name="redmi_buds_5_pro_transparency_regular">Regular</string>
<string name="redmi_buds_5_pro_transparency_voice">Enhance Voices</string>
<string name="redmi_buds_5_pro_transparency_ambient">Enhance Ambient Sounds</string>
<string name="redmi_buds_5_pro_combo_anc_off">ANC / Off</string>
<string name="redmi_buds_5_pro_combo_transparency_off">Transparency / Off</string>
<string name="redmi_buds_5_pro_combo_anc_transparency">ANC / Transparency</string>
<string name="redmi_buds_5_pro_combo_all">ANC / Transparency / Off</string>
<string name="redmi_buds_5_pro_double_connection">Double Connection</string>
<string name="redmi_buds_5_pro_double_connection_description">Allow the earbuds to connect to two devices at the same time</string>
<!-- <string name="redmi_buds_5_pro_spatial_audio">Spatial Audio</string>-->
<!-- <string name="redmi_buds_5_pro_spatial_audio_mode">Spatial Audio Mode</string>-->
<string name="redmi_buds_5_pro_adaptive_sound">Adaptive Sound</string>
<string name="redmi_buds_5_pro_adaptive_sound_description">Adjusts the sound according to the ear shape and the environment</string>
<string name="redmi_buds_5_pro_equalizer_preset_standard">Standard</string>
<string name="redmi_buds_5_pro_equalizer_preset_treble">Enhance Treble</string>
<string name="redmi_buds_5_pro_equalizer_preset_bass">Enhance Bass</string>
<string name="redmi_buds_5_pro_equalizer_preset_voice">Enhance Voice</string>
<string name="redmi_buds_5_pro_equalizer_preset_custom">Custom</string>
<string name="redmi_buds_5_pro_equalizer_band_62">62 Hz</string>
<string name="redmi_buds_5_pro_equalizer_band_125">125 Hz</string>
<string name="redmi_buds_5_pro_equalizer_band_250">250 Hz</string>
<string name="redmi_buds_5_pro_equalizer_band_500">500 Hz</string>
<string name="redmi_buds_5_pro_equalizer_band_1k">1 kHz</string>
<string name="redmi_buds_5_pro_equalizer_band_2k">2 kHz</string>
<string name="redmi_buds_5_pro_equalizer_band_4k">4 kHz</string>
<string name="redmi_buds_5_pro_equalizer_band_8k">8 kHz</string>
<string name="redmi_buds_5_pro_equalizer_band_12k">12 kHz</string>
<string name="redmi_buds_5_pro_equalizer_band_16k">16 kHz</string>
<string name="redmi_buds_5_pro_equalizer_neg6">-6 dB</string>
<string name="redmi_buds_5_pro_equalizer_neg5">-5 dB</string>
<string name="redmi_buds_5_pro_equalizer_neg4">-4 dB</string>
<string name="redmi_buds_5_pro_equalizer_neg3">-3 dB</string>
<string name="redmi_buds_5_pro_equalizer_neg2">-2 dB</string>
<string name="redmi_buds_5_pro_equalizer_neg1">-1 dB</string>
<string name="redmi_buds_5_pro_equalizer_zero">0 dB</string>
<string name="redmi_buds_5_pro_equalizer_1">1 dB</string>
<string name="redmi_buds_5_pro_equalizer_2">2 dB</string>
<string name="redmi_buds_5_pro_equalizer_3">3 dB</string>
<string name="redmi_buds_5_pro_equalizer_4">4 dB</string>
<string name="redmi_buds_5_pro_equalizer_5">5 dB</string>
<string name="redmi_buds_5_pro_equalizer_6">6 dB</string>
<string name="sony_ambient_sound">Mode</string>
<string name="sony_ambient_sound_off">Off</string>
<string name="sony_ambient_sound_noise_cancelling">Noise Cancelling</string>
@ -2674,6 +2720,8 @@
<string name="pref_adaptive_volume_control_title">Adaptive volume control</string>
<string name="pref_adaptive_noise_cancelling_title">Adaptive ANC</string>
<string name="pref_adaptive_noise_cancelling_summary">Set the strength of the ANC automatically depending on the ambient sound level</string>
<!-- <string name="pref_personalized_noise_cancelling_title">Personalized ANC</string>-->
<!-- <string name="pref_personalized_noise_cancelling_summary">Set whether to use personalized active noise cancelling</string>-->
<string name="sony_speak_to_chat">Speak-to-chat</string>
<string name="sony_speak_to_chat_summary">Turn off noise cancelling automatically when you start talking.</string>
<string name="sony_speak_to_chat_sensitivity">Voice Detection Sensitivity</string>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_key_header_redmibuds5pro_system"
android:title="@string/pref_header_system">
<PreferenceScreen
android:icon="@drawable/ic_touch"
android:key="pref_screen_redmibuds5pro_touch_options"
android:persistent="false"
android:title="@string/prefs_galaxy_touch_options">
<PreferenceCategory android:title="@string/single_tap">
<ListPreference
android:entries="@array/redmi_buds_5_pro_single_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_single_button_function_values"
android:icon="@drawable/ic_filter_1"
android:defaultValue="8"
android:key="pref_redmi_buds_5_pro_control_single_tap_left"
android:summary="%s"
android:title="@string/prefs_left" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_single_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_single_button_function_values"
android:defaultValue="8"
android:key="pref_redmi_buds_5_pro_control_single_tap_right"
android:summary="%s"
android:title="@string/prefs_right" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/double_tap">
<ListPreference
android:entries="@array/redmi_buds_5_pro_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_button_function_values"
android:icon="@drawable/ic_filter_2"
android:defaultValue="1"
android:key="pref_redmi_buds_5_pro_control_double_tap_left"
android:summary="%s"
android:title="@string/prefs_left" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_button_function_values"
android:defaultValue="1"
android:key="pref_redmi_buds_5_pro_control_double_tap_right"
android:summary="%s"
android:title="@string/prefs_right" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/triple_tap">
<ListPreference
android:entries="@array/redmi_buds_5_pro_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_button_function_values"
android:icon="@drawable/ic_filter_3"
android:defaultValue="2"
android:key="pref_redmi_buds_5_pro_control_triple_tap_left"
android:summary="%s"
android:title="@string/prefs_left" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_button_function_names"
android:entryValues="@array/redmi_buds_5_pro_button_function_values"
android:defaultValue="3"
android:key="pref_redmi_buds_5_pro_control_triple_tap_right"
android:summary="%s"
android:title="@string/prefs_right" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/long_press">
<ListPreference
android:entries="@array/redmi_buds_5_pro_long_button_mode_names"
android:entryValues="@array/redmi_buds_5_pro_long_button_mode_values"
android:icon="@drawable/ic_touch"
android:defaultValue="6"
android:key="pref_redmi_buds_5_pro_control_long_tap_mode_left"
android:summary="%s"
android:title="@string/sony_button_mode_left" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_long_button_settings_names"
android:entryValues="@array/redmi_buds_5_pro_long_button_settings_values"
android:defaultValue="7"
android:key="pref_redmi_buds_5_pro_control_long_tap_settings_left"
android:summary="%s"
android:title="@string/sony_ambient_sound_control_button_modes" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_long_button_mode_names"
android:entryValues="@array/redmi_buds_5_pro_long_button_mode_values"
android:icon="@drawable/ic_touch"
android:defaultValue="6"
android:key="pref_redmi_buds_5_pro_control_long_tap_mode_right"
android:summary="%s"
android:title="@string/sony_button_mode_right" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_long_button_settings_names"
android:entryValues="@array/redmi_buds_5_pro_long_button_settings_values"
android:defaultValue="7"
android:key="pref_redmi_buds_5_pro_control_long_tap_settings_right"
android:summary="%s"
android:title="@string/sony_ambient_sound_control_button_modes" />
</PreferenceCategory>
</PreferenceScreen>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_redmi_buds_5_pro_wearing_detection"
android:layout="@layout/preference_checkbox"
android:icon="@drawable/ic_pause"
android:summary="@string/nothing_prefs_inear_summary"
android:title="@string/nothing_prefs_inear_title"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_phone"
android:key="pref_redmi_buds_5_pro_auto_reply_phonecall"
android:summary="@string/pref_auto_reply_calls_summary"
android:title="@string/pref_auto_reply_calls_title"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_redmi_buds_5_pro_double_connection"
android:summary="@string/redmi_buds_5_pro_double_connection_description"
android:title="@string/redmi_buds_5_pro_double_connection"/>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_key_header_redmibuds5pro_ambient_sound_control"
android:title="@string/pref_header_sony_ambient_sound_control">
<ListPreference
android:defaultValue="0"
android:entries="@array/redmi_buds_5_pro_ambient_sound_control_names"
android:entryValues="@array/redmi_buds_5_pro_ambient_sound_control_values"
android:icon="@drawable/ic_hearing"
android:key="pref_redmi_buds_5_pro_ambient_sound_control"
android:summary="%s"
android:title="@string/sony_ambient_sound" />
<ListPreference
android:defaultValue="0"
android:dependency="pref_redmi_buds_5_pro_adaptive_noise_cancelling"
android:entries="@array/redmi_buds_5_pro_noise_cancelling_strength_names"
android:entryValues="@array/redmi_buds_5_pro_noise_cancelling_strength_values"
android:key="pref_redmi_buds_5_pro_noise_cancelling_strength"
android:summary="%s"
android:title="@string/prefs_active_noise_cancelling_level" />
<ListPreference
android:defaultValue="0"
android:entries="@array/redmi_buds_5_pro_transparency_strength_names"
android:entryValues="@array/redmi_buds_5_pro_transparency_strength_values"
android:key="pref_redmi_buds_5_pro_transparency_strength"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_transparency_strength" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:disableDependentsState="true"
android:key="pref_redmi_buds_5_pro_adaptive_noise_cancelling"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_adaptive_noise_cancelling_summary"
android:title="@string/pref_adaptive_noise_cancelling_title" />
<!-- <SwitchPreferenceCompat-->
<!-- android:defaultValue="true"-->
<!-- android:key="pref_redmi_buds_5_pro_personalized_noise_cancelling"-->
<!-- android:layout="@layout/preference_checkbox"-->
<!-- android:summary="@string/pref_personalized_noise_cancelling_summary"-->
<!-- android:title="@string/pref_personalized_noise_cancelling_title" />-->
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="pref_key_header_redmibuds5pro_sound"
android:title="@string/pref_header_sound">
<PreferenceScreen
android:icon="@drawable/ic_graphic_eq"
android:key="pref_sony_equalizer"
android:persistent="false"
android:title="@string/pref_header_equalizer">
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_presets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_presets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_preset"
android:summary="%s"
android:title="@string/prefs_equalizer_preset" />
<PreferenceCategory
android:key="pref_key_header_redmibuds5pro_custom_equalizer"
android:title="@string/soundcore_equalizer_custom_title">
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_62"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_62" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_125"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_125" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_250"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_250" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_500"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_500" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_1k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_1k" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_2k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_2k" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_4k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_4k" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_8k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_8k" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_12k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_12k" />
<ListPreference
android:entries="@array/redmi_buds_5_pro_equalizer_offsets_names"
android:entryValues="@array/redmi_buds_5_pro_equalizer_offsets_values"
android:icon="@drawable/ic_graphic_eq"
android:defaultValue="0"
android:key="pref_redmi_buds_5_pro_equalizer_band_16k"
android:summary="%s"
android:title="@string/redmi_buds_5_pro_equalizer_band_16k" />
</PreferenceCategory>
</PreferenceScreen>
<!-- <SwitchPreferenceCompat-->
<!-- android:defaultValue="false"-->
<!-- android:key="pref_redmi_buds_5_pro_surround_sound"-->
<!-- android:icon="@drawable/ic_surround"-->
<!-- android:layout="@layout/preference_checkbox"-->
<!-- android:title="@string/redmi_buds_5_pro_spatial_audio" />-->
<!-- <ListPreference-->
<!-- android:entries="@array/redmi_buds_5_pro_surround_sound_mode_names"-->
<!-- android:entryValues="@array/redmi_buds_5_pro_surround_sound_mode_values"-->
<!-- android:key="pref_redmi_buds_5_pro_surround_sound_mode"-->
<!-- android:summary="%s"-->
<!-- android:title="@string/redmi_buds_5_pro_spatial_audio_mode" />-->
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_redmi_buds_5_pro_adaptive_sound"
android:layout="@layout/preference_checkbox"
android:summary="@string/redmi_buds_5_pro_adaptive_sound_description"
android:title="@string/redmi_buds_5_pro_adaptive_sound" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>