From 6881bab143a21e50bb3da330d9538adaed3b8584 Mon Sep 17 00:00:00 2001 From: NTeditor Date: Wed, 17 Jun 2026 14:07:58 +0300 Subject: [PATCH] oppo: add AncConfigType.TOUCH_CYCLE_MODES --- .../oppo/OppoHeadphonesCoordinator.java | 1 + .../oppo/OppoHeadphonesPreferences.java | 3 +- .../OppoHeadphonesSettingsCustomizer.java | 1 + .../devices/oppo/OppoHeadphonesProtocol.java | 96 ++++++++++++++----- .../devices/oppo/OppoHeadphonesSupport.java | 4 +- .../devices/oppo/commands/AncConfigType.java | 46 +++++++++ .../devices/oppo/commands/AncConfigValue.java | 35 +++++++ app/src/main/res/values/arrays.xml | 2 +- ...ings_oppo_headphones_touch_options_anc.xml | 17 ++++ 9 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigType.java create mode 100644 app/src/main/res/xml/devicesettings_oppo_headphones_touch_options_anc.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 aaf281c9d9..84b8744162 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 @@ -83,6 +83,7 @@ public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceC } if (this.supportsAnc(device)) { settings.addSubScreen(DeviceSpecificSettingsScreen.AUDIO, R.xml.devicesettings_onemore_noise_control_selector); + settings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_oppo_headphones_touch_options_anc); } } 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 94b3c1e136..a73bbdb31b 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,12 +22,13 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchC import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType; public class OppoHeadphonesPreferences { - public static final String TOUCH_PREFIX= "oppo_touch__"; + 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 final String ANC_SELECTOR = "noise_control_selector"; + public static final String ANC_TOUCH_CYCLE_MODES = "oppo_touch_anc_cycle_modes"; public static String getTouchKey(final TouchConfigSide side, final TouchConfigType type) { return String.format( 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 c765390eee..db75cb9a33 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 @@ -93,6 +93,7 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC this.addPreferenceHandler(handler, OppoHeadphonesPreferences.MULTIPOINT, supportsMultipoint); this.addPreferenceHandler(handler, OppoHeadphonesPreferences.GAME_MODE, supportsGameMode); this.addPreferenceHandler(handler, OppoHeadphonesPreferences.ANC_SELECTOR, supportsAnc); + this.addPreferenceHandler(handler, OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, supportsAnc); for (final Map.Entry, List> e : touchOptions.entrySet()) { final TouchConfigSide side = e.getKey().first; 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 94e7e26a77..6a956ea172 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 @@ -26,6 +26,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.EnumSet; +import java.util.Set; +import java.util.HashSet; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; @@ -42,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchC 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.MiscConfigType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigType; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigValue; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs; @@ -344,16 +348,16 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { private static GBDeviceEvent parseMiscConfig(final byte[] payload) { final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(); for (int i = 2; i + 1 < payload.length; i += 2) { - final int code = payload[i] & 0xff; - final int value = payload[i + 1] & 0xff; + final int typeCode = payload[i] & 0xff; + final int valueCode = payload[i + 1] & 0xff; - final MiscConfigType type = MiscConfigType.fromCode(code); + final MiscConfigType type = MiscConfigType.fromCode(typeCode); if (type == null) { - LOG.warn("Unknown misc config code {}", code); + LOG.warn("Unknown misc config type code {}", typeCode); continue; } - final boolean isEnabled = (value == 1); + final boolean isEnabled = (valueCode == 1); switch (type) { case LDAC: LOG.debug("Got misc config for LDAC = {}", isEnabled); @@ -377,7 +381,7 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { ); break; default: - LOG.warn("Unknown misc config code {}", code); + LOG.warn("Unknown misc config type code {}", typeCode); break; } } @@ -421,22 +425,39 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { private static GBDeviceEvent parseAncConfig(final byte[] payload) { final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences(); - for (int i = 2; i + 1 < payload.length; i += 2) { - final int code = payload[i] & 0xff; - final int value = payload[i + 1] & 0xff; + final int typeCode = payload[1] & 0xff; + final int valueCode = payload[3] & 0xff; - switch (code) { - case 0x01: - final AncConfigValue mode = AncConfigValue.fromCode(value); - if (mode != null) { - event.withPreference(OppoHeadphonesPreferences.ANC_SELECTOR, mode.getPrefId()); - } else { - LOG.warn("Unknown ANC mode {}", value); - } - break; - default: - LOG.debug("Unknown code {}", code); - break; + final AncConfigType type = AncConfigType.fromCode(typeCode); + if (type == null) { + LOG.warn("Unknown anc type code {}", typeCode); + return event; + } + + switch (type) { + case MODE: { + final AncConfigValue mode = AncConfigValue.fromCode(valueCode); + if (mode != null) { + LOG.debug("Got anc config for {} = {}", type, mode); + event.withPreference(OppoHeadphonesPreferences.ANC_SELECTOR, mode.getPrefId()); + } else { + LOG.warn("Unknown anc mode code {}", valueCode); + } + break; + } + case TOUCH_CYCLE_MODES: { + final EnumSet modes = AncConfigValue.fromMask(valueCode); + if (!modes.isEmpty()) { + final Set prefIds = AncConfigValue.toPrefIds(modes); + + LOG.debug("Got anc config for {} = {}", type, prefIds); + event.withPreference(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, prefIds); + } + break; + } + default: { + LOG.debug("Unknown anc type code {}", typeCode); + break; } } return event; @@ -498,7 +519,19 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { mode = AncConfigValue.OFF; } LOG.debug("Sending ANC mode = {}", mode); - return encodeAncConfigSet(mode); + return encodeAncModeSet(mode); + } + + if (config.equals(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES)) { + final Set prefIds = prefs.getStringSet(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, Collections.emptySet()); + final EnumSet modes = AncConfigValue.fromPrefIds(prefIds); + if (modes.size() < 2) { + LOG.warn("ANC cycle must contain at least 2 modes. Current selection: {}", modes); + return super.encodeSendConfiguration(config); + } + + LOG.debug("Sending ANC touch cycle modes = {}", modes); + return encodeAncTouchCycleModesSet(modes); } return super.encodeSendConfiguration(config); @@ -563,18 +596,31 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol { return encodeMessage(OppoCommand.MISC_CONFIG_REQ, payload); } - public byte[] encodeAncConfigSet(final AncConfigValue mode) { + public byte[] encodeAncModeSet(final AncConfigValue mode) { final byte[] payload = new byte[] { - (byte) 0x01, + (byte) AncConfigType.MODE.getCode(), (byte) 0x01, (byte) mode.getCode() }; return encodeMessage(OppoCommand.ANC_CONFIG_SET, payload); } - public byte[] encodeAncConfigReq() { + public byte[] encodeAncTouchCycleModesSet(final EnumSet modes) { + int mask = 0; + for (AncConfigValue mode : modes) { + mask |= mode.getCode(); + } final byte[] payload = new byte[] { + (byte) AncConfigType.TOUCH_CYCLE_MODES.getCode(), (byte) 0x01, + (byte) mask + }; + return encodeMessage(OppoCommand.ANC_CONFIG_SET, payload); + } + + public byte[] encodeAncConfigReq(final AncConfigType type) { + final byte[] payload = new byte[] { + (byte) type.getCode(), (byte) 0x01, }; return encodeMessage(OppoCommand.ANC_CONFIG_REQ, payload); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java index 0a89a78201..f21ddd9f0b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesSupport.java @@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractHeadphoneSerialDeviceSupportV2; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.MiscConfigType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigType; import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoHeadphonesCoordinator; public class OppoHeadphonesSupport extends AbstractHeadphoneSerialDeviceSupportV2 { @@ -91,7 +92,8 @@ public class OppoHeadphonesSupport extends AbstractHeadphoneSerialDeviceSupportV } builder.write(mDeviceProtocol.encodeTouchConfigReq()); if (coordinator.supportsAnc(getDevice())) { - builder.write(mDeviceProtocol.encodeAncConfigReq()); + builder.write(mDeviceProtocol.encodeAncConfigReq(AncConfigType.MODE)); + builder.write(mDeviceProtocol.encodeAncConfigReq(AncConfigType.TOUCH_CYCLE_MODES)); } builder.write(mDeviceProtocol.encodeBatteryReq()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigType.java new file mode 100644 index 0000000000..b176da6966 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigType.java @@ -0,0 +1,46 @@ +/* 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 AncConfigType { + MODE(0x01), + TOUCH_CYCLE_MODES(0x02), + ; + + private final int code; + + AncConfigType(final int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + @Nullable + public static AncConfigType fromCode(final int code) { + for (final AncConfigType param : AncConfigType.values()) { + if (param.code == code) { + return param; + } + } + + return null; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigValue.java index 9c2550a889..cd4b69681f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigValue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/AncConfigValue.java @@ -16,6 +16,11 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands; +import java.lang.Iterable; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; + import androidx.annotation.Nullable; public enum AncConfigValue { @@ -51,6 +56,17 @@ public enum AncConfigValue { return prefId; } + public static EnumSet fromMask(int mask) { + EnumSet modes = EnumSet.noneOf(AncConfigValue.class); + for (AncConfigValue mode : AncConfigValue.values()) { + if ((mask & mode.getCode()) == mode.getCode()) { + modes.add(mode); + } + } + + return modes; + } + @Nullable public static AncConfigValue fromPrefId(final String prefId) { for (final AncConfigValue param : AncConfigValue.values()) { @@ -61,4 +77,23 @@ public enum AncConfigValue { return null; } + + public static Set toPrefIds(Iterable modes) { + Set prefIds = new HashSet<>(); + for (AncConfigValue mode : modes) { + prefIds.add(mode.getPrefId()); + } + return prefIds; + } + + public static EnumSet fromPrefIds(Iterable prefIds) { + EnumSet modes = EnumSet.noneOf(AncConfigValue.class); + for (String prefId : prefIds) { + AncConfigValue mode = fromPrefId(prefId); + if (mode != null) { + modes.add(mode); + } + } + return modes; + } } diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 5625c3eae0..3311f4cae1 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -4520,7 +4520,7 @@ @string/pref_title_touch_voice_assistant @string/pref_title_touch_voice_assistant @string/prefs_game_mode - @string/prefs_noise_control + @string/moondrop_touch_action_anc_mode diff --git a/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options_anc.xml b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options_anc.xml new file mode 100644 index 0000000000..cdbf5de2d8 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options_anc.xml @@ -0,0 +1,17 @@ + + + + + + +