1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-22 06:41:06 +02:00

Sony LinkBuds: Initial support

This commit is contained in:
José Rebelo 2024-03-15 23:36:50 +00:00
parent 5798466e55
commit fcc930749e
19 changed files with 420 additions and 22 deletions

View File

@ -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";

View File

@ -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);

View File

@ -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,
}

View File

@ -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<SonyHeadphonesCapabilities, Integer>() {{
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<SonyHeadphonesCapabilities, Integer>() {{
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);

View File

@ -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

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<SonyHeadphonesCapabilities> 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;
}
}

View File

@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
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;

View File

@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
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,

View File

@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
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,

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<String, Object> toPreferences() {
return new HashMap<String, Object>() {{
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));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<String, Object> toPreferences() {
return new HashMap<String, Object>() {{
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));
}
}

View File

@ -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),

View File

@ -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);

View File

@ -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);

View File

@ -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<SonyHeadphonesCapabilities, Request> capabilityEntry : capabilityRequestMap.entrySet()) {

View File

@ -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<? extends GBDeviceEvent> 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<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
if (payload.length != 4) {
@ -653,6 +727,40 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
return Collections.singletonList(event);
}
@Override
public List<? extends GBDeviceEvent> 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<? extends GBDeviceEvent> 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<? extends GBDeviceEvent> 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<? extends GBDeviceEvent> 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

View File

@ -1521,6 +1521,7 @@
<string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
<string name="devicetype_sony_wf_1000xm5">Sony WF-1000XM5</string>
<string name="devicetype_sony_wi_sp600n">Sony WI-SP600N</string>
<string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
<string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
<string name="devicetype_binary_sensor">Binary sensor</string>
<string name="devicetype_honor_band3">Honor Band 3</string>
@ -2174,6 +2175,7 @@
<string name="pref_header_stress">Stress</string>
<string name="pref_header_spo2">Blood Oxygen</string>
<string name="pref_header_sony_ambient_sound_control">Ambient Sound Control</string>
<string name="pref_header_sony_sound_control">Sound Control</string>
<string name="pref_header_sony_device_info">Device Information</string>
<string name="stress_relaxed">Relaxed</string>
<string name="stress_mild">Mild</string>
@ -2238,6 +2240,10 @@
<string name="sony_equalizer_clear_bass">Clear Bass</string>
<string name="sony_audio_upsampling">Audio Upsampling</string>
<string name="sony_touch_sensor">Touch sensor control</string>
<string name="pref_wide_area_tap_summary">Recognize taps between cheeks and ears</string>
<string name="pref_wide_area_tap_title">Wide area tap</string>
<string name="pref_adaptive_volume_control_summary">Increase volume automatically when ambient sound is loud</string>
<string name="pref_adaptive_volume_control_title">Adaptive volume control</string>
<string name="sony_speak_to_chat">Speak-to-chat</string>
<string name="sony_speak_to_chat_summary">Turn off noise cancelling automatically when you start talking.</string>
<string name="sony_speak_to_chat_sensitivity">Voice Detection Sensitivity</string>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_key_header_sony_ambient_sound_control"
android:title="@string/pref_header_sony_sound_control">
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_hearing"
android:key="pref_adaptive_volume_control"
android:summary="@string/pref_adaptive_volume_control_summary"
android:title="@string/pref_adaptive_volume_control_title" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_touch"
android:key="pref_wide_area_tap"
android:summary="@string/pref_wide_area_tap_summary"
android:title="@string/pref_wide_area_tap_title" />
</androidx.preference.PreferenceScreen>