From 0c11ba44f827e6ff2b28ad69dbe6f0f0c01434da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Thu, 21 May 2026 09:51:14 +0100 Subject: [PATCH] CMF Buds 2a: Add touch options --- .../nothing/AbstractEarCoordinator.java | 22 +++- .../devices/nothing/CmfBuds2aCoordinator.java | 5 + .../nothing/EarSettingsCustomizer.java | 10 ++ .../service/devices/nothing/Ear1Support.java | 103 +++++++++++++++--- .../nothing/NothingBudsPreferences.java | 12 ++ .../devices/nothing/NothingTapAction.java | 37 +++++++ .../devices/nothing/NothingTapType.java | 31 ++++++ .../main/res/drawable/ic_double_tap_hold.xml | 10 ++ app/src/main/res/values/arrays.xml | 25 +++++ app/src/main/res/values/strings.xml | 2 + .../devicesettings_cmf_buds_touch_options.xml | 80 ++++++++++++++ 11 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingBudsPreferences.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapAction.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapType.java create mode 100644 app/src/main/res/drawable/ic_double_tap_hold.xml create mode 100644 app/src/main/res/xml/devicesettings_cmf_buds_touch_options.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java index 638dc0345a..20ef5a8b25 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/AbstractEarCoordinator.java @@ -24,6 +24,7 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig; @@ -68,17 +69,24 @@ public abstract class AbstractEarCoordinator extends AbstractBLClassicDeviceCoor @Override public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings(); - deviceSpecificSettings.addRootScreen(R.xml.devicesettings_nothing_ear1); + final List audio = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.AUDIO); + audio.add(R.xml.devicesettings_nothing_ear1); if (!getEqualizerPresets().isEmpty()) { - deviceSpecificSettings.addRootScreen(R.xml.devicesettings_nothing_equalizer); + audio.add(R.xml.devicesettings_nothing_equalizer); } if (supportsUltraBass()) { - deviceSpecificSettings.addRootScreen(R.xml.devicesettings_nothing_ultra_bass); + audio.add(R.xml.devicesettings_nothing_ultra_bass); + } + if (supportsTouchOptions()) { + final List touchOptions = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS); + touchOptions.add(R.xml.devicesettings_cmf_buds_touch_options); } if (supportsLowLatency()) { - deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones_low_latency); + final List connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION); + connection.add(R.xml.devicesettings_headphones_low_latency); } - deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones); + final List callsAndNotif = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS); + callsAndNotif.add(R.xml.devicesettings_headphones); return deviceSpecificSettings; } @@ -117,4 +125,8 @@ public abstract class AbstractEarCoordinator extends AbstractBLClassicDeviceCoor public boolean supportsUltraBass() { return false; } + + public boolean supportsTouchOptions() { + return false; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBuds2aCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBuds2aCoordinator.java index b5f20183e0..cfec0d202d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBuds2aCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/CmfBuds2aCoordinator.java @@ -90,4 +90,9 @@ public class CmfBuds2aCoordinator extends AbstractEarCoordinator { public boolean supportsUltraBass() { return true; } + + @Override + public boolean supportsTouchOptions() { + return true; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java index 7cea16c9e9..7b9e5a173d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/nothing/EarSettingsCustomizer.java @@ -30,6 +30,7 @@ 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.service.devices.nothing.NothingBudsPreferences; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer { @@ -116,6 +117,15 @@ public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer { listPreference.setEntryValues(entryValues.toArray(new CharSequence[0])); } } + + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_3); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_1_HOLD); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2_HOLD); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_3); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_1_HOLD); + handler.addPreferenceHandlerFor(NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2_HOLD); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java index 99a5c572c2..988d312b69 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/Ear1Support.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -159,6 +160,19 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { ) ); break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_3: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_1_HOLD: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2_HOLD: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_3: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_1_HOLD: + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2_HOLD: + sendCommand( + "set touch options " + config, + nothingProtocol.encodeTouchOptions(config, prefs) + ); + break; default: LOG.debug("CONFIG: " + config); break; @@ -224,11 +238,13 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { private final int bitmask; - NothingAudioMode (int bitmask) { + NothingAudioMode(int bitmask) { this.bitmask = bitmask; } - public int getBitmask() { return bitmask; } + public int getBitmask() { + return bitmask; + } public static NothingAudioMode fromBitmask(int bitmask) { for (NothingAudioMode flag : values()) { @@ -386,6 +402,7 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { evt.fwVersion = new String(payload); return evt; } + private GBDeviceEventUpdatePreferences handleAudioModeStatus(byte[] payload) { final GBDeviceEventUpdatePreferences preferencesEvent = new GBDeviceEventUpdatePreferences(); @@ -500,6 +517,58 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { return encodeAudioModeStatusReq(); } + public byte[] encodeTouchOptions(final String key, SharedPreferences prefs) { + final String actionString = prefs.getString(key, NothingTapAction.OFF.name()); + final NothingTapAction action = NothingTapAction.valueOf(actionString.toUpperCase(Locale.ROOT)); + final byte side; + final NothingTapType tapType; + switch (key) { + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2: + side = battery_earphone_left; + tapType = NothingTapType.TAP_2; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_3: + side = battery_earphone_left; + tapType = NothingTapType.TAP_3; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_1_HOLD: + side = battery_earphone_left; + tapType = NothingTapType.TAP_1_HOLD; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__LEFT__TAP_2_HOLD: + side = battery_earphone_left; + tapType = NothingTapType.TAP_2_HOLD; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2: + side = battery_earphone_right; + tapType = NothingTapType.TAP_2; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_3: + side = battery_earphone_right; + tapType = NothingTapType.TAP_3; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_1_HOLD: + side = battery_earphone_right; + tapType = NothingTapType.TAP_1_HOLD; + break; + case NothingBudsPreferences.PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2_HOLD: + side = battery_earphone_right; + tapType = NothingTapType.TAP_2_HOLD; + break; + default: + LOG.error("Unknown touch option preference key {}", key); + return null; + } + + return new byte[]{ + 0x01, + side, + 0x01, + (byte) tapType.getCode(), + (byte) action.getCode() + }; + } + private List handleBatteryInfo(byte[] payload) { List batEvts = new ArrayList<>(); @@ -518,9 +587,9 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { If one of the batteries is recharging, we consider the battery as recharging. */ - // GBDeviceEventBatteryInfo evBattery = new GBDeviceEventBatteryInfo(); - // evBattery.level = 0; - // boolean batteryCharging = false; + // GBDeviceEventBatteryInfo evBattery = new GBDeviceEventBatteryInfo(); + // evBattery.level = 0; + // boolean batteryCharging = false; int numBatteries = payload[0]; for (int i = 0; i < numBatteries; i++) { @@ -531,17 +600,17 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { batEvts.add(batteries.get(payload[1 + 2 * i])); - // evBattery.level += (short) ((payload[2 + 2 * i] & MASK_BATTERY) / numBatteries); - // if (!batteryCharging) { - // batteryCharging = ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING); - // } - // LOG.debug("single battery level: " + hexdump(payload, 2+2*i,1) +"-"+ ((payload[2+2*i] & 0xff))+":" + evBattery.level); - } + // evBattery.level += (short) ((payload[2 + 2 * i] & MASK_BATTERY) / numBatteries); + // if (!batteryCharging) { + // batteryCharging = ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING); + // } + // LOG.debug("single battery level: " + hexdump(payload, 2+2*i,1) +"-"+ ((payload[2+2*i] & 0xff))+":" + evBattery.level); + } - // evBattery.state = BatteryState.UNKNOWN; - // evBattery.state = batteryCharging ? BatteryState.BATTERY_CHARGING : evBattery.state; + // evBattery.state = BatteryState.UNKNOWN; + // evBattery.state = batteryCharging ? BatteryState.BATTERY_CHARGING : evBattery.state; - // return evBattery; + // return evBattery; return batEvts; } @@ -568,9 +637,9 @@ public class Ear1Support extends AbstractHeadphoneBTBRDeviceSupport { batteries.put(battery_earphone_right, new GBDeviceEventBatteryInfo()); batteries.put(battery_case, new GBDeviceEventBatteryInfo()); - batteries.get(battery_case).batteryIndex=0; - batteries.get(battery_earphone_left).batteryIndex=1; - batteries.get(battery_earphone_right).batteryIndex=2; + batteries.get(battery_case).batteryIndex = 0; + batteries.get(battery_earphone_left).batteryIndex = 1; + batteries.get(battery_earphone_right).batteryIndex = 2; this.incrementCounter = incrementCounter; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingBudsPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingBudsPreferences.java new file mode 100644 index 0000000000..f9954488bd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingBudsPreferences.java @@ -0,0 +1,12 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing; + +public class NothingBudsPreferences { + public static final String PREF_CMF_BUDS_TOUCH__LEFT__TAP_2 = "cmf_buds_touch__left__tap_2"; + public static final String PREF_CMF_BUDS_TOUCH__LEFT__TAP_3 = "cmf_buds_touch__left__tap_3"; + public static final String PREF_CMF_BUDS_TOUCH__LEFT__TAP_1_HOLD = "cmf_buds_touch__left__tap_1_hold"; + public static final String PREF_CMF_BUDS_TOUCH__LEFT__TAP_2_HOLD = "cmf_buds_touch__left__tap_2_hold"; + public static final String PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2 = "cmf_buds_touch__right__tap_2"; + public static final String PREF_CMF_BUDS_TOUCH__RIGHT__TAP_3 = "cmf_buds_touch__right__tap_3"; + public static final String PREF_CMF_BUDS_TOUCH__RIGHT__TAP_1_HOLD = "cmf_buds_touch__right__tap_1_hold"; + public static final String PREF_CMF_BUDS_TOUCH__RIGHT__TAP_2_HOLD = "cmf_buds_touch__right__tap_2_hold"; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapAction.java new file mode 100644 index 0000000000..114fc97888 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapAction.java @@ -0,0 +1,37 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing; + +import androidx.annotation.Nullable; + +public enum NothingTapAction { + OFF(0x01), + PREVIOUS_TRACK(0x08), + NEXT_TRACK(0x09), + VOICE_ASSISTANT(0x0b), + VOLUME_UP(0x12), + VOLUME_DOWN(0x13), + ANC_MODE__ANC_TRANSPARENCY_OFF(0x0a), + ANC_MODE__ANC_TRANSPARENCY(0x16), + ANC_MODE__ANC_OFF(0x14), + ANC_MODE__TRANSPARENCY_OFF(0x15), + ; + + private final int code; + + NothingTapAction(final int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + @Nullable + public static NothingTapAction fromCode(final int code) { + for (NothingTapAction type : values()) { + if (type.code == code) { + return type; + } + } + return null; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapType.java new file mode 100644 index 0000000000..687a1cc016 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingTapType.java @@ -0,0 +1,31 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.nothing; + +import androidx.annotation.Nullable; + +public enum NothingTapType { + TAP_2(0x02), + TAP_3(0x03), + TAP_1_HOLD(0x07), + TAP_2_HOLD(0x09), + ; + + private final int code; + + NothingTapType(final int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + @Nullable + public static NothingTapType fromCode(final int code) { + for (NothingTapType type : values()) { + if (type.code == code) { + return type; + } + } + return null; + } +} diff --git a/app/src/main/res/drawable/ic_double_tap_hold.xml b/app/src/main/res/drawable/ic_double_tap_hold.xml new file mode 100644 index 0000000000..cb5212a63b --- /dev/null +++ b/app/src/main/res/drawable/ic_double_tap_hold.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7a7cfb88ea..c0bb1f1c64 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -4535,6 +4535,31 @@ game_mode + + @string/sony_button_mode_off + @string/pref_media_previous + @string/pref_media_next + @string/pref_media_volumeup + @string/pref_media_volumedown + @string/pref_title_touch_voice_assistant + @string/redmi_buds_5_pro_combo_all + @string/redmi_buds_5_pro_combo_anc_transparency + @string/redmi_buds_5_pro_combo_anc_off + @string/redmi_buds_5_pro_combo_transparency_off + + + off + previous_track + next_track + volume_up + volume_down + voice_assistant + anc_mode__anc_transparency_off + anc_mode__anc_transparency + anc_mode__anc_off + anc_mode__transparency_off + + @string/prefs_active_noise_cancelling_transport @string/prefs_active_noise_cancelling_indoor diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66bffb49ae..62f63415e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3602,6 +3602,8 @@ Double Tap Triple Tap Long Press + Tap and Hold + Double Tap and Hold Continue Pressing Quick Attention Custom widget diff --git a/app/src/main/res/xml/devicesettings_cmf_buds_touch_options.xml b/app/src/main/res/xml/devicesettings_cmf_buds_touch_options.xml new file mode 100644 index 0000000000..7c210572ba --- /dev/null +++ b/app/src/main/res/xml/devicesettings_cmf_buds_touch_options.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + +