1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-24 17:45:50 +01:00

Sony WH-1000XM4: Add speak-to-chat settings

This commit is contained in:
José Rebelo 2023-03-24 00:03:54 +00:00
parent d7ecfcb16b
commit f2a7f6ab45
10 changed files with 247 additions and 27 deletions

View File

@ -23,7 +23,8 @@ public enum SonyHeadphonesCapabilities {
PowerOffFromPhone,
AmbientSoundControl,
WindNoiseReduction,
SpeakToChat,
SpeakToChatEnabled,
SpeakToChatConfig,
AncOptimizer,
AudioSettingsOnlyOnSbcCodec,
AudioUpsampling,

View File

@ -178,7 +178,9 @@ public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceC
settings.add(R.xml.devicesettings_sony_headphones_ambient_sound_control);
}
if (supports(SonyHeadphonesCapabilities.SpeakToChat)) {
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);
}

View File

@ -47,11 +47,11 @@ public class SonyWH1000XM4Coordinator extends SonyHeadphonesCoordinator {
return Arrays.asList(
// TODO: Function of [CUSTOM] button
// TODO R.xml.devicesettings_connect_two_devices,
// TODO R.xml.devicesettings_sony_headphones_speak_to_chat_with_settings,
SonyHeadphonesCapabilities.BatterySingle,
SonyHeadphonesCapabilities.AmbientSoundControl,
SonyHeadphonesCapabilities.WindNoiseReduction,
SonyHeadphonesCapabilities.SpeakToChat,
SonyHeadphonesCapabilities.SpeakToChatEnabled,
SonyHeadphonesCapabilities.SpeakToChatConfig,
SonyHeadphonesCapabilities.AncOptimizer,
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
SonyHeadphonesCapabilities.AudioUpsampling,

View File

@ -0,0 +1,124 @@
/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
import android.content.SharedPreferences;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
public class SpeakToChatConfig {
public enum Sensitivity {
AUTO(0x00),
HIGH(0x01),
LOW(0x02),
;
private final byte code;
Sensitivity(final int code) {
this.code = (byte) code;
}
public byte getCode() {
return this.code;
}
public static Sensitivity fromCode(final byte b) {
for (Sensitivity value : Sensitivity.values()) {
if (value.getCode() == b) {
return value;
}
}
return null;
}
}
public enum Timeout {
SHORT(0x00),
STANDARD(0x01),
LONG(0x02),
OFF(0x03),
;
private final byte code;
Timeout(final int code) {
this.code = (byte) code;
}
public byte getCode() {
return this.code;
}
public static Timeout fromCode(final byte b) {
for (Timeout value : Timeout.values()) {
if (value.getCode() == b) {
return value;
}
}
return null;
}
}
private final boolean focusOnVoice;
private final Sensitivity sensitivity;
private final Timeout timeout;
public SpeakToChatConfig(final boolean focusOnVoice, final Sensitivity sensitivity, final Timeout timeout) {
this.focusOnVoice = focusOnVoice;
this.sensitivity = sensitivity;
this.timeout = timeout;
}
public boolean isFocusOnVoice() {
return focusOnVoice;
}
public Sensitivity getSensitivity() {
return sensitivity;
}
public Timeout getTimeout() {
return timeout;
}
public String toString() {
return String.format(Locale.getDefault(), "SpeakToChatConfig{focusOnVoice=%s, sensitivity=%s, timeout=%s}", focusOnVoice, sensitivity, timeout);
}
public Map<String, Object> toPreferences() {
return new HashMap<String, Object>() {{
put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE, focusOnVoice);
put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY, sensitivity.name().toLowerCase(Locale.getDefault()));
put(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT, timeout.name().toLowerCase(Locale.getDefault()));
}};
}
public static SpeakToChatConfig fromPreferences(final SharedPreferences prefs) {
final boolean focusOnVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE, false);
final Sensitivity sensitivity = Sensitivity.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY, "auto").toUpperCase());
final Timeout timeout = Timeout.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT, "standard").toUpperCase());
return new SpeakToChatConfig(focusOnVoice, sensitivity, timeout);
}
}

View File

@ -24,15 +24,15 @@ import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
public class SpeakToChat {
public class SpeakToChatEnabled {
private final boolean enabled;
public SpeakToChat(final boolean enabled) {
public SpeakToChatEnabled(final boolean enabled) {
this.enabled = enabled;
}
public String toString() {
return String.format(Locale.getDefault(), "SpeakToChat{enabled=%s}", enabled);
return String.format(Locale.getDefault(), "SpeakToChatEnabled{enabled=%s}", enabled);
}
public boolean isEnabled() {
@ -45,9 +45,9 @@ public class SpeakToChat {
}};
}
public static SpeakToChat fromPreferences(final SharedPreferences prefs) {
public static SpeakToChatEnabled fromPreferences(final SharedPreferences prefs) {
final boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT, false);
return new SpeakToChat(enabled);
return new SpeakToChatEnabled(enabled);
}
}

View File

@ -40,7 +40,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.Equali
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChat;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
@ -221,10 +222,12 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
LOG.warn("Connection to two devices not implemented ('{}')", config);
return super.encodeSendConfiguration(config);
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT:
configRequest = protocolImpl.setSpeakToChatEnabled(SpeakToChatEnabled.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY:
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE:
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT:
configRequest = protocolImpl.setSpeakToChat(SpeakToChat.fromPreferences(prefs));
configRequest = protocolImpl.setSpeakToChatConfig(SpeakToChatConfig.fromPreferences(prefs));
break;
default:
LOG.warn("Unknown config '{}'", config);

View File

@ -30,7 +30,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.Equali
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChat;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
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;
@ -59,9 +60,13 @@ public abstract class AbstractSonyProtocolImpl {
public abstract Request setAmbientSoundControl(final AmbientSoundControl config);
public abstract Request setSpeakToChat(final SpeakToChat config);
public abstract Request setSpeakToChatEnabled(final SpeakToChatEnabled config);
public abstract Request getSpeakToChat();
public abstract Request getSpeakToChatEnabled();
public abstract Request setSpeakToChatConfig(final SpeakToChatConfig config);
public abstract Request getSpeakToChatConfig();
public abstract Request getNoiseCancellingOptimizerState();

View File

@ -75,6 +75,11 @@ public enum PayloadTypeV1 {
AUTOMATIC_POWER_OFF_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xf8),
AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xf9),
SPEAK_TO_CHAT_CONFIG_GET(MessageType.COMMAND_1, 0xfa),
SPEAK_TO_CHAT_CONFIG_RET(MessageType.COMMAND_1, 0xfb),
SPEAK_TO_CHAT_CONFIG_SET(MessageType.COMMAND_1, 0xfc),
SPEAK_TO_CHAT_CONFIG_NOTIFY(MessageType.COMMAND_1, 0xfd),
// TODO: The headphones spit out a lot of json, analyze it
JSON_GET(MessageType.COMMAND_1, 0xc4),
JSON_RET(MessageType.COMMAND_1, 0xc9),

View File

@ -50,7 +50,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.Equali
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChat;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
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;
@ -141,7 +142,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
}
@Override
public Request setSpeakToChat(final SpeakToChat config) {
public Request setSpeakToChatEnabled(final SpeakToChatEnabled config) {
return new Request(
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
new byte[]{
@ -154,7 +155,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
}
@Override
public Request getSpeakToChat() {
public Request getSpeakToChatEnabled() {
return new Request(
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
new byte[]{
@ -164,6 +165,32 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
);
}
@Override
public Request setSpeakToChatConfig(final SpeakToChatConfig config) {
return new Request(
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getMessageType(),
new byte[]{
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getCode(),
(byte) 0x05,
(byte) 0x00,
config.getSensitivity().getCode(),
(byte) (config.isFocusOnVoice() ? 0x01 : 0x00),
config.getTimeout().getCode()
}
);
}
@Override
public Request getSpeakToChatConfig() {
return new Request(
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getMessageType(),
new byte[]{
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getCode(),
(byte) 0x05
}
);
}
@Override
public Request getNoiseCancellingOptimizerState() {
return new Request(
@ -530,6 +557,9 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
case AUTOMATIC_POWER_OFF_BUTTON_MODE_RET:
case AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY:
return handleAutomaticPowerOffButtonMode(payload);
case SPEAK_TO_CHAT_CONFIG_RET:
case SPEAK_TO_CHAT_CONFIG_NOTIFY:
return handleSpeakToChatConfig(payload);
case VOICE_NOTIFICATIONS_RET:
case VOICE_NOTIFICATIONS_NOTIFY:
return handleVoiceNotifications(payload);
@ -567,7 +597,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
put(SonyHeadphonesCapabilities.EqualizerWithCustomBands, getEqualizer());
put(SonyHeadphonesCapabilities.SoundPosition, getSoundPosition());
put(SonyHeadphonesCapabilities.SurroundMode, getSurroundMode());
put(SonyHeadphonesCapabilities.SpeakToChat, getSpeakToChat());
put(SonyHeadphonesCapabilities.SpeakToChatEnabled, getSpeakToChatEnabled());
put(SonyHeadphonesCapabilities.SpeakToChatConfig, getSpeakToChatConfig());
put(SonyHeadphonesCapabilities.PauseWhenTakenOff, getPauseWhenTakenOff());
put(SonyHeadphonesCapabilities.AmbientSoundControlButtonMode, getAmbientSoundControlButtonMode());
put(SonyHeadphonesCapabilities.QuickAccess, getQuickAccess());
@ -733,7 +764,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
return Collections.singletonList(event);
}
public List<? extends GBDeviceEvent> handleSpeakToChat(final byte[] payload) {
public List<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
if (payload.length != 4) {
LOG.warn("Unexpected payload length {}", payload.length);
return Collections.emptyList();
@ -748,7 +779,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
LOG.debug("Speak to chat: {}", enabled);
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
.withPreferences(new SpeakToChat(enabled).toPreferences());
.withPreferences(new SpeakToChatEnabled(enabled).toPreferences());
return Collections.singletonList(event);
}
@ -954,7 +985,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
case 0x03:
return handlePauseWhenTakenOff(payload);
case 0x05:
return handleSpeakToChat(payload);
return handleSpeakToChatEnabled(payload);
case 0x06:
return handleButtonModes(payload);
}
@ -1068,6 +1099,45 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
return Collections.singletonList(event);
}
public List<? extends GBDeviceEvent> handleSpeakToChatConfig(final byte[] payload) {
if (payload.length != 6) {
LOG.warn("Unexpected payload length {}", payload.length);
return Collections.emptyList();
}
if (payload[1] != 0x05) {
LOG.warn("Not speak to chat config, ignoring");
return Collections.emptyList();
}
final SpeakToChatConfig.Sensitivity sensitivity = SpeakToChatConfig.Sensitivity.fromCode(payload[3]);
if (sensitivity == null) {
LOG.warn("Unknown sensitivity code {}", String.format("%02x", payload[3]));
return Collections.emptyList();
}
final Boolean focusOnVoice = booleanFromByte(payload[4]);
if (focusOnVoice == null) {
LOG.warn("Unknown focus on voice code {}", String.format("%02x", payload[3]));
return Collections.emptyList();
}
final SpeakToChatConfig.Timeout timeout = SpeakToChatConfig.Timeout.fromCode(payload[5]);
if (timeout == null) {
LOG.warn("Unknown timeout code {}", String.format("%02x", payload[3]));
return Collections.emptyList();
}
final SpeakToChatConfig speakToChatConfig = new SpeakToChatConfig(focusOnVoice, 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> handleVoiceNotifications(final byte[] payload) {
if (payload.length != 4) {
LOG.warn("Unexpected payload length {}", payload.length);

View File

@ -20,7 +20,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -28,7 +27,6 @@ import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
@ -41,12 +39,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.Equali
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChat;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.PayloadTypeV1;
@ -106,13 +104,25 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
}
@Override
public Request setSpeakToChat(SpeakToChat config) {
public Request setSpeakToChatEnabled(SpeakToChatEnabled config) {
LOG.warn("Speak-to-chat not implemented for V2");
return null;
}
@Override
public Request getSpeakToChat() {
public Request getSpeakToChatEnabled() {
LOG.warn("Speak-to-chat not implemented for V2");
return null;
}
@Override
public Request setSpeakToChatConfig(SpeakToChatConfig config) {
LOG.warn("Speak-to-chat not implemented for V2");
return null;
}
@Override
public Request getSpeakToChatConfig() {
LOG.warn("Speak-to-chat not implemented for V2");
return null;
}