From 54f0949f8447966c56d9042373aa78705fb99dcc Mon Sep 17 00:00:00 2001 From: NTeditor Date: Tue, 16 Jun 2026 08:25:40 +0300 Subject: [PATCH] oppo: add minimal support ldac, multipoint and gamemode --- .../oppo/OppoHeadphonesCoordinator.java | 27 +++++++++- .../oppo/OppoHeadphonesPreferences.java | 11 +++- .../OppoHeadphonesSettingsCustomizer.java | 37 +++++++++++-- .../devices/oppo/OppoHeadphonesProtocol.java | 53 +++++++++++++++++-- .../devices/oppo/commands/OppoCommand.java | 2 + .../devices/oppo/commands/PrefType.java | 48 +++++++++++++++++ ...vicesettings_oppo_headphones_game_mode.xml | 9 ++++ ...icesettings_oppo_headphones_multipoint.xml | 9 ++++ 8 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/PrefType.java create mode 100644 app/src/main/res/xml/devicesettings_oppo_headphones_game_mode.xml create mode 100644 app/src/main/res/xml/devicesettings_oppo_headphones_multipoint.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java index b487941b40..e4f757aa69 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java @@ -76,12 +76,25 @@ public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceC settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS); settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones); + settings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER); + if (this.supportsLdac(device)) { + settings.addSubScreen(DeviceSpecificSettingsScreen.DEVELOPER, R.xml.devicesettings_ldac_toggle); + } + + if (this.supportsMultipoint(device)) { + settings.addSubScreen(DeviceSpecificSettingsScreen.DEVELOPER, R.xml.devicesettings_oppo_headphones_multipoint); + } + + if (this.supportsGameMode(device)) { + settings.addSubScreen(DeviceSpecificSettingsScreen.DEVELOPER, R.xml.devicesettings_oppo_headphones_game_mode); + } + return settings; } @Override public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) { - return new OppoHeadphonesSettingsCustomizer(getTouchOptions()); + return new OppoHeadphonesSettingsCustomizer(getTouchOptions(), supportsLdac(device), supportsMultipoint(device), supportsGameMode(device)); } protected abstract Map, List> getTouchOptions(); @@ -90,4 +103,16 @@ public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceC public final DeviceKind getDeviceKind(@NonNull GBDevice device) { return DeviceKind.EARBUDS; } + + public boolean supportsLdac(@NonNull GBDevice device) { + return false; + } + + public boolean supportsMultipoint(@NonNull GBDevice device) { + return false; + } + + public boolean supportsGameMode(@NonNull GBDevice device) { + return false; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java index c5538e8cd9..3cbfa6ac2e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesPreferences.java @@ -22,10 +22,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchC import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType; public class OppoHeadphonesPreferences { - public static String getKey(final TouchConfigSide side, final TouchConfigType type) { + public static final String TOUCH_PREFIX= "oppo_touch__"; + + public static final String LDAC = "pref_soundcore_ldac_mode"; + public static final String MULTIPOINT = "oppo_multipoint"; + public static final String GAME_MODE= "oppo_game_mode"; + + public static String getTouchKey(final TouchConfigSide side, final TouchConfigType type) { return String.format( Locale.ROOT, - "oppo_touch__%s__%s", + "%s%s__%s", + TOUCH_PREFIX, side.name().toLowerCase(Locale.ROOT), type.name().toLowerCase(Locale.ROOT) ); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java index c081027a16..301ee15414 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java @@ -40,10 +40,17 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer { private final Map, List> touchOptions; + private final boolean supportsLdac; + private final boolean supportsMultipoint; + private final boolean supportsGameMode; public static final Creator CREATOR = new Creator() { @Override public OppoHeadphonesSettingsCustomizer createFromParcel(final Parcel in) { + final boolean supportsLdac = in.readByte() != 0; + final boolean supportsMultipoint = in.readByte() != 0; + final boolean supportsGameMode = in.readByte() != 0; + final Map, List> touchOptions = new LinkedHashMap<>(); final int numOptions = in.readInt(); for (int i = 0; i < numOptions; i++) { @@ -53,7 +60,8 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC in.readList(values, TouchConfigValue.class.getClassLoader()); touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values); } - return new OppoHeadphonesSettingsCustomizer(touchOptions); + + return new OppoHeadphonesSettingsCustomizer(touchOptions, supportsLdac, supportsMultipoint, supportsGameMode); } @Override @@ -62,8 +70,11 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC } }; - public OppoHeadphonesSettingsCustomizer(final Map, List> touchOptions) { + public OppoHeadphonesSettingsCustomizer(final Map, List> touchOptions, final boolean supportsLdac, final boolean supportsMultipoint, final boolean supportsGameMode) { this.touchOptions = touchOptions; + this.supportsLdac = supportsLdac; + this.supportsMultipoint = supportsMultipoint; + this.supportsGameMode = supportsGameMode; } @Override @@ -75,6 +86,10 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC final Set knownSides = new HashSet<>(); final Set knownTypes = new HashSet<>(); + this.addPreferenceHandler(handler, OppoHeadphonesPreferences.LDAC, supportsLdac); + this.addPreferenceHandler(handler, OppoHeadphonesPreferences.MULTIPOINT, supportsMultipoint); + this.addPreferenceHandler(handler, OppoHeadphonesPreferences.GAME_MODE, supportsGameMode); + for (final Map.Entry, List> e : touchOptions.entrySet()) { final TouchConfigSide side = e.getKey().first; final TouchConfigType type = e.getKey().second; @@ -83,7 +98,7 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC knownSides.add(side); knownTypes.add(type); - final String key = OppoHeadphonesPreferences.getKey(side, type); + final String key = OppoHeadphonesPreferences.getTouchKey(side, type); final ListPreference pref = handler.findPreference(key); if (pref == null) { continue; @@ -120,7 +135,7 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC for (final TouchConfigType type : TouchConfigType.values()) { if (!knownTypes.contains(type)) { - final String key = OppoHeadphonesPreferences.getKey(side, type); + final String key = OppoHeadphonesPreferences.getTouchKey(side, type); final Preference pref = handler.findPreference(key); if (pref != null) { pref.setVisible(false); @@ -130,6 +145,16 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC } } + private void addPreferenceHandler(DeviceSpecificSettingsHandler handler, String key, boolean isSupported) { + Preference pref = handler.findPreference(key); + if (pref != null) { + pref.setVisible(isSupported); + if (isSupported) { + handler.addPreferenceHandlerFor(key); + } + } + } + @Override public Set getPreferenceKeysWithSummary() { return Collections.emptySet(); @@ -142,6 +167,10 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC @Override public void writeToParcel(final Parcel dest, final int flags) { + dest.writeByte((byte) (supportsLdac ? 1 : 0)); + dest.writeByte((byte) (supportsMultipoint ? 1 : 0)); + dest.writeByte((byte) (supportsGameMode ? 1 : 0)); + dest.writeInt(touchOptions.size()); for (final Map.Entry, List> e : touchOptions.entrySet()) { dest.writeString(e.getKey().first.name()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java index 2c2bd7ed46..48bb4ced73 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java @@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.OppoCo import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue; +import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.PrefType; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs; @@ -262,7 +263,7 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { LOG.debug("Got touch config for {} {} = {}", side, type, value); eventUpdatePreferences.withPreference( - OppoHeadphonesPreferences.getKey(side, type), + OppoHeadphonesPreferences.getTouchKey(side, type), value.name().toLowerCase(Locale.ROOT) ); } @@ -325,11 +326,11 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { public byte[] encodeSendConfiguration(final String config) { final DevicePrefs prefs = getDevicePrefs(); - if (config.startsWith("oppo_touch__")) { + if (config.startsWith(OppoHeadphonesPreferences.TOUCH_PREFIX)) { final String[] parts = config.split("__"); final TouchConfigSide side = TouchConfigSide.valueOf(parts[1].toUpperCase(Locale.ROOT)); final TouchConfigType type = TouchConfigType.valueOf(parts[2].toUpperCase(Locale.ROOT)); - final String valueCode = prefs.getString(OppoHeadphonesPreferences.getKey(side, type), null); + final String valueCode = prefs.getString(OppoHeadphonesPreferences.getTouchKey(side, type), null); if (valueCode == null) { LOG.warn("Failed to get touch option value for {}/{}", side, type); return super.encodeSendConfiguration(config); @@ -348,6 +349,24 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { return encodeMessage(OppoCommand.TOUCH_CONFIG_SET, buf.array()); } + if (config.equals(OppoHeadphonesPreferences.LDAC)) { + final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.LDAC, false); + LOG.debug("Sending ldac = {}", value); + return encodeLdacSet(value); + } + + if (config.equals(OppoHeadphonesPreferences.MULTIPOINT)) { + final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.MULTIPOINT, false); + LOG.debug("Sending multipoint = {}", value); + return encodeMultipointSet(value); + } + + if (config.equals(OppoHeadphonesPreferences.GAME_MODE)) { + final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.GAME_MODE, false); + LOG.debug("Sending game mode = {}", value); + return encodeGameModeSet(value); + } + return super.encodeSendConfiguration(config); } @@ -359,6 +378,34 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { return encodeMessage(OppoCommand.TOUCH_CONFIG_REQ, new byte[]{0x02, 0x03, 0x01}); } + public byte[] encodeLdacSet(final boolean enable) { + final byte[] payload = new byte[] { + (byte) (enable ? 0x01 : 0x00) + }; + return encodePrefSet(PrefType.LDAC, payload); + } + + public byte[] encodeGameModeSet(final boolean enable) { + final byte[] payload = new byte[] { + (byte) (enable ? 0x01 : 0x00) + }; + return encodePrefSet(PrefType.GAME_MODE, payload); + } + + public byte[] encodeMultipointSet(final boolean enable) { + final byte[] payload = new byte[] { + (byte) (enable ? 0x01 : 0x00) + }; + return encodePrefSet(PrefType.MULTIPOINT, payload); + } + + public byte[] encodePrefSet(final PrefType type, final byte[] value) { + final byte[] payload = new byte[1 + value.length]; + payload[0] = (byte) type.getCode(); + System.arraycopy(value, 0, payload, 1, value.length); + return encodeMessage(OppoCommand.PREF_SET, payload); + } + private byte[] encodeMessage(final OppoCommand command, final byte[] payload) { final ByteBuffer buf = ByteBuffer.allocate(9 + payload.length).order(ByteOrder.LITTLE_ENDIAN); buf.put(CMD_PREAMBLE); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java index 0d594c835e..5657952ebf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/OppoCommand.java @@ -30,6 +30,8 @@ public enum OppoCommand { TOUCH_CONFIG_ACK(0x8401), FIND_DEVICE_REQ(0x0400), FIND_DEVICE_ACK(0x8400), + PREF_SET(0x0403), + PREF_ACK(0x8403), ; private final short code; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/PrefType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/PrefType.java new file mode 100644 index 0000000000..149822e179 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/PrefType.java @@ -0,0 +1,48 @@ +/* Copyright (C) 2024 José Rebelo + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands; + +import androidx.annotation.Nullable; + +public enum PrefType { + LDAC(0x18), + GAME_MODE(0x06), + MULTIPOINT(0x11), + FIND_PHONE(0x26), + ; + + private final int code; + + PrefType(final int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + @Nullable + public static PrefType fromCode(final int code) { + for (final PrefType param : PrefType.values()) { + if (param.code == code) { + return param; + } + } + + return null; + } +} diff --git a/app/src/main/res/xml/devicesettings_oppo_headphones_game_mode.xml b/app/src/main/res/xml/devicesettings_oppo_headphones_game_mode.xml new file mode 100644 index 0000000000..aa8b0430ec --- /dev/null +++ b/app/src/main/res/xml/devicesettings_oppo_headphones_game_mode.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_oppo_headphones_multipoint.xml b/app/src/main/res/xml/devicesettings_oppo_headphones_multipoint.xml new file mode 100644 index 0000000000..2fb23735ec --- /dev/null +++ b/app/src/main/res/xml/devicesettings_oppo_headphones_multipoint.xml @@ -0,0 +1,9 @@ + + + +