From fcc930749e00272c079d2dc4dd9ea3fe944987e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 15 Mar 2024 23:36:50 +0000 Subject: [PATCH] Sony LinkBuds: Initial support --- .../DeviceSettingsPreferenceConst.java | 2 + .../DeviceSpecificSettingsFragment.java | 2 + .../SonyHeadphonesCapabilities.java | 3 + .../headphones/SonyHeadphonesCoordinator.java | 17 +- .../SonyHeadphonesSettingsCustomizer.java | 10 ++ .../coordinators/SonyLinkBudsCoordinator.java | 73 +++++++++ .../SonyLinkBudsSCoordinator.java | 6 - .../SonyWH1000XM4Coordinator.java | 5 +- .../SonyWH1000XM5Coordinator.java | 5 +- .../prefs/AdaptiveVolumeControl.java | 46 ++++++ .../sony/headphones/prefs/WideAreaTap.java | 46 ++++++ .../gadgetbridge/model/DeviceType.java | 2 + .../headphones/SonyHeadphonesProtocol.java | 9 ++ .../impl/AbstractSonyProtocolImpl.java | 11 ++ .../protocol/impl/v1/SonyProtocolImplV1.java | 28 ++++ .../protocol/impl/v2/SonyProtocolImplV2.java | 148 +++++++++++++++++- app/src/main/res/values/strings.xml | 6 + ...ony_headphones_adaptive_volume_control.xml | 14 ++ ...settings_sony_headphones_wide_area_tap.xml | 9 ++ 19 files changed, 420 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java create mode 100644 app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml create mode 100644 app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 751e372be..13b35b881 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -373,6 +373,8 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE = "pref_sony_speak_to_chat_focus_on_voice"; public static final String PREF_SONY_SPEAK_TO_CHAT_TIMEOUT = "pref_sony_speak_to_chat_timeout"; public static final String PREF_SONY_CONNECT_TWO_DEVICES = "pref_sony_connect_two_devices"; + public static final String PREF_SONY_ADAPTIVE_VOLUME_CONTROL = "pref_adaptive_volume_control"; + public static final String PREF_SONY_WIDE_AREA_TAP = "pref_wide_area_tap"; public static final String PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 7ffbbadc0..3cc59ceaf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -533,6 +533,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE); addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_TIMEOUT); addPreferenceHandlerFor(PREF_SONY_CONNECT_TWO_DEVICES); + addPreferenceHandlerFor(PREF_SONY_ADAPTIVE_VOLUME_CONTROL); + addPreferenceHandlerFor(PREF_SONY_WIDE_AREA_TAP); addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE); addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java index b715db9d8..0c48a33c7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCapabilities.java @@ -25,6 +25,7 @@ public enum SonyHeadphonesCapabilities { WindNoiseReduction, SpeakToChatEnabled, SpeakToChatConfig, + SpeakToChatFocusOnVoice, AncOptimizer, AudioSettingsOnlyOnSbcCodec, AudioUpsampling, @@ -41,4 +42,6 @@ public enum SonyHeadphonesCapabilities { QuickAccess, PauseWhenTakenOff, Volume, + WideAreaTap, + AdaptiveVolumeControl, } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java index 6ce91f958..453fc710e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesCoordinator.java @@ -176,17 +176,21 @@ public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceC settings.add(R.xml.devicesettings_sony_headphones_ambient_sound_control); } - if (supports(SonyHeadphonesCapabilities.SpeakToChatConfig)) { - settings.add(R.xml.devicesettings_sony_headphones_speak_to_chat_with_settings); - } else if (supports(SonyHeadphonesCapabilities.SpeakToChatEnabled)) { - settings.add(R.xml.devicesettings_sony_headphones_speak_to_chat_simple); - } - if (supports(SonyHeadphonesCapabilities.AncOptimizer)) { settings.add(R.xml.devicesettings_sony_headphones_anc_optimizer); } } + if (supports(SonyHeadphonesCapabilities.AdaptiveVolumeControl)) { + settings.add(R.xml.devicesettings_sony_headphones_adaptive_volume_control); + } + + if (supports(SonyHeadphonesCapabilities.SpeakToChatConfig)) { + settings.add(R.xml.devicesettings_sony_headphones_speak_to_chat_with_settings); + } else if (supports(SonyHeadphonesCapabilities.SpeakToChatEnabled)) { + settings.add(R.xml.devicesettings_sony_headphones_speak_to_chat_simple); + } + addSettingsUnderHeader(settings, R.xml.devicesettings_header_other, new LinkedHashMap() {{ put(SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec, R.xml.devicesettings_sony_warning_wh1000xm3); put(SonyHeadphonesCapabilities.EqualizerSimple, R.xml.devicesettings_sony_headphones_equalizer); @@ -198,6 +202,7 @@ public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceC }}); addSettingsUnderHeader(settings, R.xml.devicesettings_header_system, new LinkedHashMap() {{ + put(SonyHeadphonesCapabilities.WideAreaTap, R.xml.devicesettings_sony_headphones_wide_area_tap); put(SonyHeadphonesCapabilities.ButtonModesLeftRight, R.xml.devicesettings_sony_headphones_button_modes_left_right); put(SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, R.xml.devicesettings_sony_headphones_ambient_sound_control_button_modes); put(SonyHeadphonesCapabilities.QuickAccess, R.xml.devicesettings_sony_headphones_quick_access); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java index 3524fc0fb..6c480da47 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/SonyHeadphonesSettingsCustomizer.java @@ -32,6 +32,7 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_START; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_STATUS; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE; import android.app.ProgressDialog; @@ -54,6 +55,7 @@ import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; @@ -117,6 +119,8 @@ public class SonyHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC @Override public void customizeSettings(final DeviceSpecificSettingsHandler handler, Prefs prefs) { + final SonyHeadphonesCoordinator coordinator = (SonyHeadphonesCoordinator) device.getDeviceCoordinator(); + // Only enable the focus on voice check and voice level slider if the ambient sound control mode is ambient sound final ListPreference ambientSoundControl = handler.findPreference(PREF_SONY_AMBIENT_SOUND_CONTROL); @@ -185,6 +189,12 @@ public class SonyHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC } }); } + + // Hide unsupported preferences + final Preference speakToChatFocusVoice = handler.findPreference(PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE); + if (speakToChatFocusVoice != null && !coordinator.supports(SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice)) { + speakToChatFocusVoice.setVisible(false); + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java new file mode 100644 index 000000000..43116c0d3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsCoordinator.java @@ -0,0 +1,73 @@ +/* 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.devices.sony.headphones.coordinators; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; + +public class SonyLinkBudsCoordinator extends SonyHeadphonesCoordinator { + @Override + protected Pattern getSupportedDeviceName() { + // TODO fully exclude LE_LinkBuds? + return Pattern.compile("^LinkBuds$"); + } + + @Override + public List getCapabilities() { + return Arrays.asList( + SonyHeadphonesCapabilities.BatteryDual, + SonyHeadphonesCapabilities.BatteryCase, + SonyHeadphonesCapabilities.SpeakToChatEnabled, + SonyHeadphonesCapabilities.SpeakToChatConfig, + SonyHeadphonesCapabilities.AdaptiveVolumeControl, + SonyHeadphonesCapabilities.EqualizerWithCustomBands, + SonyHeadphonesCapabilities.AudioUpsampling, + SonyHeadphonesCapabilities.ButtonModesLeftRight, + SonyHeadphonesCapabilities.PauseWhenTakenOff, + SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff, + SonyHeadphonesCapabilities.WideAreaTap, + SonyHeadphonesCapabilities.VoiceNotifications + // TODO spacial sound optimization + // TODO factory reset + ); + } + + @Override + public boolean preferServiceV2() { + return true; + } + + @Override + public int getDeviceNameResource() { + return R.string.devicetype_sony_linkbuds; + } + + @Override + public int getDefaultIconResource() { + return R.drawable.ic_device_galaxy_buds; + } + + @Override + public int getDisabledIconResource() { + return R.drawable.ic_device_galaxy_buds_disabled; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java index 566d7eff2..e77ace49e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyLinkBudsSCoordinator.java @@ -16,8 +16,6 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators; -import androidx.annotation.NonNull; - import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -25,8 +23,6 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class SonyLinkBudsSCoordinator extends SonyHeadphonesCoordinator { @Override @@ -55,13 +51,11 @@ public class SonyLinkBudsSCoordinator extends SonyHeadphonesCoordinator { return true; } - @Override public int getDeviceNameResource() { return R.string.devicetype_sony_linkbuds_s; } - @Override public int getDefaultIconResource() { return R.drawable.ic_device_galaxy_buds; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java index 1ca26c7f1..0ed65d3f3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM4Coordinator.java @@ -16,8 +16,6 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators; -import androidx.annotation.NonNull; - import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -25,8 +23,6 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class SonyWH1000XM4Coordinator extends SonyHeadphonesCoordinator { @Override @@ -49,6 +45,7 @@ public class SonyWH1000XM4Coordinator extends SonyHeadphonesCoordinator { SonyHeadphonesCapabilities.WindNoiseReduction, SonyHeadphonesCapabilities.SpeakToChatEnabled, SonyHeadphonesCapabilities.SpeakToChatConfig, + SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice, SonyHeadphonesCapabilities.AncOptimizer, SonyHeadphonesCapabilities.EqualizerWithCustomBands, SonyHeadphonesCapabilities.AudioUpsampling, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java index b15504642..c137ebef0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/coordinators/SonyWH1000XM5Coordinator.java @@ -16,8 +16,6 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators; -import androidx.annotation.NonNull; - import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; @@ -25,8 +23,6 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class SonyWH1000XM5Coordinator extends SonyHeadphonesCoordinator { @Override @@ -49,6 +45,7 @@ public class SonyWH1000XM5Coordinator extends SonyHeadphonesCoordinator { SonyHeadphonesCapabilities.AmbientSoundControl, SonyHeadphonesCapabilities.SpeakToChatEnabled, SonyHeadphonesCapabilities.SpeakToChatConfig, + SonyHeadphonesCapabilities.SpeakToChatFocusOnVoice, // TODO SonyHeadphonesCapabilities.AudioUpsampling, // TODO SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, SonyHeadphonesCapabilities.VoiceNotifications, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.java new file mode 100644 index 000000000..96f14612e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/AdaptiveVolumeControl.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.devices.sony.headphones.prefs; + +import android.content.SharedPreferences; + +import java.util.HashMap; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; + +public class AdaptiveVolumeControl { + private final boolean enabled; + + public AdaptiveVolumeControl(final boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public Map toPreferences() { + return new HashMap() {{ + put(DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL, enabled); + }}; + } + + public static AdaptiveVolumeControl fromPreferences(final SharedPreferences prefs) { + return new AdaptiveVolumeControl(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL, true)); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.java new file mode 100644 index 000000000..0e9b08eec --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/headphones/prefs/WideAreaTap.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.devices.sony.headphones.prefs; + +import android.content.SharedPreferences; + +import java.util.HashMap; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; + +public class WideAreaTap { + private final boolean enabled; + + public WideAreaTap(final boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public Map toPreferences() { + return new HashMap() {{ + put(DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP, enabled); + }}; + } + + public static WideAreaTap fromPreferences(final SharedPreferences prefs) { + return new WideAreaTap(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP, true)); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index cb2cbbc27..e89e63619 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -150,6 +150,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.soflow.SoFlowCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator; @@ -335,6 +336,7 @@ public enum DeviceType { SONY_WF_1000XM3(SonyWF1000XM3Coordinator.class), SONY_WH_1000XM2(SonyWH1000XM2Coordinator.class), SONY_WF_1000XM4(SonyWF1000XM4Coordinator.class), + SONY_LINKBUDS(SonyLinkBudsCoordinator.class), SONY_LINKBUDS_S(SonyLinkBudsSCoordinator.class), SONY_WH_1000XM5(SonyWH1000XM5Coordinator.class), SONY_WF_1000XM5(SonyWF1000XM5Coordinator.class), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java index e76e0680f..b117dd21c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling; @@ -47,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceN import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message; @@ -126,6 +128,7 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol { // LinkBuds S 2.0.2: 01:00:03:00:00:07:00:00 // WH-1000XM5 1.1.3: 01:00:03:00:00:00:00:00 // WF-1000XM5 2.0.1: 01:00:03:00:10:04:00:00 + // LinkBuds 1.0.3: 01:00:02:00:10:00:00:00 protocolVersion = "v2"; } else { LOG.error("Unexpected init response payload length: {}", message.getPayload().length); @@ -260,6 +263,12 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol { case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT: configRequest = protocolImpl.setSpeakToChatConfig(SpeakToChatConfig.fromPreferences(prefs)); break; + case DeviceSettingsPreferenceConst.PREF_SONY_ADAPTIVE_VOLUME_CONTROL: + configRequest = protocolImpl.setAdaptiveVolumeControl(AdaptiveVolumeControl.fromPreferences(prefs)); + break; + case DeviceSettingsPreferenceConst.PREF_SONY_WIDE_AREA_TAP: + configRequest = protocolImpl.setWideAreaTap(WideAreaTap.fromPreferences(prefs)); + break; default: LOG.warn("Unknown config '{}'", config); return super.encodeSendConfiguration(config); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java index 52d356f5e..884b4dfd7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/AbstractSonyProtocolImpl.java @@ -20,6 +20,7 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling; @@ -35,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakT import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType; @@ -60,6 +62,11 @@ public abstract class AbstractSonyProtocolImpl { public abstract Request setAmbientSoundControl(final AmbientSoundControl config); + + public abstract Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config); + + public abstract Request getAdaptiveVolumeControl(); + public abstract Request setSpeakToChatEnabled(final SpeakToChatEnabled config); public abstract Request getSpeakToChatEnabled(); @@ -84,6 +91,10 @@ public abstract class AbstractSonyProtocolImpl { public abstract Request setAutomaticPowerOff(final AutomaticPowerOff config); + public abstract Request setWideAreaTap(final WideAreaTap config); + + public abstract Request getWideAreaTap(); + public abstract Request getButtonModes(); public abstract Request setButtonModes(final ButtonModes config); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java index 532ef1f93..b37b0dbc5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v1/SonyProtocolImplV1.java @@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling; @@ -55,6 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakT import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents.SonyHeadphonesEnqueueRequestEvent; @@ -141,6 +143,18 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl { return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array()); } + @Override + public Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config) { + LOG.warn("Adaptive volume control not implemented for V1"); + return null; + } + + @Override + public Request getAdaptiveVolumeControl() { + LOG.warn("Adaptive volume control not implemented for V1"); + return null; + } + @Override public Request setSpeakToChatEnabled(final SpeakToChatEnabled config) { return new Request( @@ -284,6 +298,18 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl { ); } + @Override + public Request setWideAreaTap(final WideAreaTap config) { + LOG.warn("Adaptive volume control not implemented for V1"); + return null; + } + + @Override + public Request getWideAreaTap() { + LOG.warn("Adaptive volume control not implemented for V1"); + return null; + } + public Request getButtonModes() { return new Request( PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(), @@ -631,6 +657,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl { put(SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, getAmbientSoundControlButtonMode()); put(SonyHeadphonesCapabilities.QuickAccess, getQuickAccess()); put(SonyHeadphonesCapabilities.Volume, getVolume()); + put(SonyHeadphonesCapabilities.AdaptiveVolumeControl, getAdaptiveVolumeControl()); + put(SonyHeadphonesCapabilities.WideAreaTap, getWideAreaTap()); }}; for (Map.Entry capabilityEntry : capabilityRequestMap.entrySet()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java index 232b4c52e..d452bb582 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/protocol/impl/v2/SonyProtocolImplV2.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AdaptiveVolumeControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling; @@ -44,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakT import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor; import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications; +import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.WideAreaTap; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request; @@ -105,6 +107,29 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array()); } + @Override + public Request setAdaptiveVolumeControl(final AdaptiveVolumeControl config) { + return new Request( + PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(), + new byte[]{ + PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(), + (byte) 0x0a, + (byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...? + } + ); + } + + @Override + public Request getAdaptiveVolumeControl() { + return new Request( + PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(), + new byte[]{ + PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(), + (byte) 0x0a + } + ); + } + @Override public Request setSpeakToChatEnabled(SpeakToChatEnabled config) { return new Request( @@ -233,6 +258,30 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { ); } + @Override + public Request setWideAreaTap(final WideAreaTap config) { + return new Request( + PayloadTypeV1.TOUCH_SENSOR_SET.getMessageType(), + new byte[]{ + PayloadTypeV1.TOUCH_SENSOR_SET.getCode(), + (byte) 0xd1, + (byte) 0x00, + (byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...? + } + ); + } + + @Override + public Request getWideAreaTap() { + return new Request( + PayloadTypeV1.TOUCH_SENSOR_GET.getMessageType(), + new byte[]{ + PayloadTypeV1.TOUCH_SENSOR_GET.getCode(), + (byte) 0xd1 + } + ); + } + @Override public Request getButtonModes() { return new Request( @@ -627,6 +676,31 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { return Collections.singletonList(event); } + public List handleAdaptiveVolumeControl(final byte[] payload) { + if (payload.length != 3) { + LOG.warn("Unexpected payload length {}", payload.length); + return Collections.emptyList(); + } + + if (payload[1] != 0x0a) { + LOG.warn("Not adaptive volume control, ignoring"); + return Collections.emptyList(); + } + + final Boolean disabled = booleanFromByte(payload[2]); + if (disabled == null) { + LOG.warn("Unknown adaptive volume control code {}", String.format("%02x", payload[2])); + return Collections.emptyList(); + } + + LOG.debug("Adaptive volume control: {}", !disabled); + + final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences() + .withPreferences(new AdaptiveVolumeControl(!disabled).toPreferences()); + + return Collections.singletonList(event); + } + @Override public List handleSpeakToChatEnabled(final byte[] payload) { if (payload.length != 4) { @@ -653,6 +727,40 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { return Collections.singletonList(event); } + @Override + public List handleSpeakToChatConfig(final byte[] payload) { + if (payload.length != 4) { + LOG.warn("Unexpected payload length {}", payload.length); + return Collections.emptyList(); + } + + if (payload[1] != 0x0c) { + LOG.warn("Not speak to chat config, ignoring"); + return Collections.emptyList(); + } + + final SpeakToChatConfig.Sensitivity sensitivity = SpeakToChatConfig.Sensitivity.fromCode(payload[2]); + if (sensitivity == null) { + LOG.warn("Unknown sensitivity code {}", String.format("%02x", payload[2])); + return Collections.emptyList(); + } + + final SpeakToChatConfig.Timeout timeout = SpeakToChatConfig.Timeout.fromCode(payload[3]); + if (timeout == null) { + LOG.warn("Unknown timeout code {}", String.format("%02x", payload[3])); + return Collections.emptyList(); + } + + final SpeakToChatConfig speakToChatConfig = new SpeakToChatConfig(false, sensitivity, timeout); + + LOG.debug("Speak to chat config: {}", speakToChatConfig); + + final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences() + .withPreferences(speakToChatConfig.toPreferences()); + + return Collections.singletonList(event); + } + public List handleQuickAccess(final byte[] payload) { if (payload.length != 5) { LOG.warn("Unexpected payload length {}", payload.length); @@ -680,6 +788,11 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { } public List handleAmbientSoundControlButtonMode(final byte[] payload) { + if (payload.length == 4 && payload[1] == 0x0c) { + // FIXME split this + return handleSpeakToChatConfig(payload); + } + if (payload.length != 7) { LOG.warn("Unexpected payload length {}", payload.length); return Collections.emptyList(); @@ -815,6 +928,8 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { return handlePauseWhenTakenOff(payload); case 0x03: return handleButtonModes(payload); + case 0x0a: + return handleAdaptiveVolumeControl(payload); case 0x0c: return handleSpeakToChatEnabled(payload); case 0x0d: @@ -844,8 +959,37 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 { @Override public List handleTouchSensor(final byte[] payload) { - LOG.warn("Touch sensor not implemented for V2"); - return Collections.emptyList(); + if (payload.length != 4) { + LOG.warn("Unexpected payload length {}", payload.length); + return Collections.emptyList(); + } + + if (payload[1] != (byte) 0xd1) { + LOG.warn("Not wide area tap"); + return Collections.emptyList(); + } + + boolean enabled; + + // reversed? + switch (payload[3]) { + case 0x00: + enabled = true; + break; + case 0x01: + enabled = false; + break; + default: + LOG.warn("Unknown wide area tap code {}", String.format("%02x", payload[3])); + return Collections.emptyList(); + } + + LOG.debug("Wide Area Tap: {}", enabled); + + final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences() + .withPreferences(new WideAreaTap(enabled).toPreferences()); + + return Collections.singletonList(event); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1479f70ef..7e31adb90 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1521,6 +1521,7 @@ Sony WF-1000XM4 Sony WF-1000XM5 Sony WI-SP600N + Sony LinkBuds Sony LinkBuds S Binary sensor Honor Band 3 @@ -2174,6 +2175,7 @@ Stress Blood Oxygen Ambient Sound Control + Sound Control Device Information Relaxed Mild @@ -2238,6 +2240,10 @@ Clear Bass Audio Upsampling Touch sensor control + Recognize taps between cheeks and ears + Wide area tap + Increase volume automatically when ambient sound is loud + Adaptive volume control Speak-to-chat Turn off noise cancelling automatically when you start talking. Voice Detection Sensitivity diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml b/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml new file mode 100644 index 000000000..aed950c01 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_sony_headphones_adaptive_volume_control.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml b/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml new file mode 100644 index 000000000..3f59f9e4b --- /dev/null +++ b/app/src/main/res/xml/devicesettings_sony_headphones_wide_area_tap.xml @@ -0,0 +1,9 @@ + + + +