mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 20:36:51 +01:00
Sony WF-1000XM4: Initial support
This commit is contained in:
parent
d4ba532b11
commit
8fdfbfa97c
@ -85,7 +85,10 @@ vendor's servers.
|
||||
- PineTime (InfiniTime Firmware)
|
||||
- Roidmi, Roidmi 3, Mojietu 3 (Bluetooth FM Transmitters)
|
||||
- [SMA](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/SMA) Q2 (SMA-Q2-OSS Firmware)
|
||||
- [Sony WH-1000XM2, WH-1000XM3, WH-1000XM4, WF-SP800N, WF-1000XM3](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
|
||||
- [Sony Headphones](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
|
||||
- WH-1000XM2, WH-1000XM3, WH-1000XM4
|
||||
- WF-SP800N
|
||||
- WF-1000XM3, WF-1000XM4
|
||||
- Teclast H10, H30
|
||||
- TLW64
|
||||
- Vibratissimo (Experimental)
|
||||
|
@ -31,7 +31,8 @@ public enum SonyHeadphonesCapabilities {
|
||||
AutomaticPowerOffWhenTakenOff,
|
||||
AutomaticPowerOffByTime,
|
||||
TouchSensorSingle,
|
||||
Equalizer,
|
||||
EqualizerSimple,
|
||||
EqualizerWithCustomBands,
|
||||
SoundPosition,
|
||||
SurroundMode,
|
||||
PauseWhenTakenOff
|
||||
|
@ -43,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||
|
||||
public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceCoordinator {
|
||||
@Override
|
||||
@ -186,7 +185,8 @@ public abstract class SonyHeadphonesCoordinator extends AbstractBLClassicDeviceC
|
||||
|
||||
addSettingsUnderHeader(settings, R.xml.devicesettings_header_other, new LinkedHashMap<SonyHeadphonesCapabilities, Integer>() {{
|
||||
put(SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec, R.xml.devicesettings_sony_warning_wh1000xm3);
|
||||
put(SonyHeadphonesCapabilities.Equalizer, R.xml.devicesettings_sony_headphones_equalizer);
|
||||
put(SonyHeadphonesCapabilities.EqualizerSimple, R.xml.devicesettings_sony_headphones_equalizer);
|
||||
put(SonyHeadphonesCapabilities.EqualizerWithCustomBands, R.xml.devicesettings_sony_headphones_equalizer_with_custom_bands);
|
||||
put(SonyHeadphonesCapabilities.SoundPosition, R.xml.devicesettings_sony_headphones_sound_position);
|
||||
put(SonyHeadphonesCapabilities.SurroundMode, R.xml.devicesettings_sony_headphones_surround_mode);
|
||||
put(SonyHeadphonesCapabilities.AudioUpsampling, R.xml.devicesettings_sony_headphones_audio_upsampling);
|
||||
|
@ -62,7 +62,7 @@ public class SonyWF1000XM3Coordinator extends SonyHeadphonesCoordinator {
|
||||
SonyHeadphonesCapabilities.PowerOffFromPhone,
|
||||
SonyHeadphonesCapabilities.AmbientSoundControl,
|
||||
SonyHeadphonesCapabilities.WindNoiseReduction,
|
||||
SonyHeadphonesCapabilities.Equalizer,
|
||||
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
|
||||
SonyHeadphonesCapabilities.AudioUpsampling,
|
||||
SonyHeadphonesCapabilities.ButtonModesLeftRight,
|
||||
SonyHeadphonesCapabilities.PauseWhenTakenOff,
|
||||
|
@ -0,0 +1,71 @@
|
||||
/* Copyright (C) 2022 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.coordinators;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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.BatteryConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class SonyWF1000XM4Coordinator extends SonyHeadphonesCoordinator {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
if (candidate.getName().contains("WF-1000XM4")) {
|
||||
return DeviceType.SONY_WF_1000XM4;
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.SONY_WF_1000XM4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatteryConfig[] getBatteryConfig() {
|
||||
final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
|
||||
final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
|
||||
final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
|
||||
|
||||
return new BatteryConfig[]{battery1, battery2, battery3};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SonyHeadphonesCapabilities> getCapabilities() {
|
||||
return Arrays.asList(
|
||||
SonyHeadphonesCapabilities.BatteryDual,
|
||||
SonyHeadphonesCapabilities.BatteryCase,
|
||||
SonyHeadphonesCapabilities.AmbientSoundControl,
|
||||
SonyHeadphonesCapabilities.WindNoiseReduction,
|
||||
SonyHeadphonesCapabilities.EqualizerSimple,
|
||||
SonyHeadphonesCapabilities.AudioUpsampling,
|
||||
SonyHeadphonesCapabilities.ButtonModesLeftRight,
|
||||
SonyHeadphonesCapabilities.PauseWhenTakenOff,
|
||||
SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff
|
||||
);
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@ public class SonyWFSP800NCoordinator extends SonyHeadphonesCoordinator {
|
||||
SonyHeadphonesCapabilities.BatteryCase,
|
||||
SonyHeadphonesCapabilities.PowerOffFromPhone,
|
||||
SonyHeadphonesCapabilities.AmbientSoundControl,
|
||||
SonyHeadphonesCapabilities.Equalizer,
|
||||
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
|
||||
SonyHeadphonesCapabilities.ButtonModesLeftRight,
|
||||
SonyHeadphonesCapabilities.PauseWhenTakenOff,
|
||||
SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff,
|
||||
|
@ -50,7 +50,7 @@ public class SonyWH1000XM2Coordinator extends SonyHeadphonesCoordinator {
|
||||
SonyHeadphonesCapabilities.WindNoiseReduction,
|
||||
SonyHeadphonesCapabilities.AncOptimizer,
|
||||
SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec,
|
||||
SonyHeadphonesCapabilities.Equalizer,
|
||||
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
|
||||
SonyHeadphonesCapabilities.SoundPosition,
|
||||
SonyHeadphonesCapabilities.SurroundMode,
|
||||
SonyHeadphonesCapabilities.AudioUpsampling
|
||||
|
@ -50,7 +50,7 @@ public class SonyWH1000XM3Coordinator extends SonyHeadphonesCoordinator {
|
||||
SonyHeadphonesCapabilities.WindNoiseReduction,
|
||||
SonyHeadphonesCapabilities.AncOptimizer,
|
||||
SonyHeadphonesCapabilities.AudioSettingsOnlyOnSbcCodec,
|
||||
SonyHeadphonesCapabilities.Equalizer,
|
||||
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
|
||||
SonyHeadphonesCapabilities.SoundPosition,
|
||||
SonyHeadphonesCapabilities.SurroundMode,
|
||||
SonyHeadphonesCapabilities.AudioUpsampling,
|
||||
|
@ -52,7 +52,7 @@ public class SonyWH1000XM4Coordinator extends SonyHeadphonesCoordinator {
|
||||
SonyHeadphonesCapabilities.AmbientSoundControl,
|
||||
SonyHeadphonesCapabilities.WindNoiseReduction,
|
||||
SonyHeadphonesCapabilities.AncOptimizer,
|
||||
SonyHeadphonesCapabilities.Equalizer,
|
||||
SonyHeadphonesCapabilities.EqualizerWithCustomBands,
|
||||
SonyHeadphonesCapabilities.AudioUpsampling,
|
||||
SonyHeadphonesCapabilities.TouchSensorSingle,
|
||||
SonyHeadphonesCapabilities.PauseWhenTakenOff,
|
||||
|
@ -114,6 +114,7 @@ public enum DeviceType {
|
||||
SONY_WH_1000XM4(432, R.drawable.ic_device_sony_overhead, R.drawable.ic_device_sony_overhead_disabled, R.string.devicetype_sony_wh_1000xm4),
|
||||
SONY_WF_1000XM3(433, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_sony_wf_1000xm3),
|
||||
SONY_WH_1000XM2(434, R.drawable.ic_device_sony_overhead, R.drawable.ic_device_sony_overhead_disabled, R.string.devicetype_sony_wh_1000xm2),
|
||||
SONY_WF_1000XM4(435, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_sony_wf_1000xm4),
|
||||
BOSE_QC35(440, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_bose_qc35),
|
||||
VESC_NRF(500, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
|
||||
VESC_HM10(501, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
|
||||
|
@ -327,6 +327,8 @@ public class DeviceSupportFactory {
|
||||
return new ServiceDeviceSupport(new SonyHeadphonesSupport());
|
||||
case SONY_WH_1000XM2:
|
||||
return new ServiceDeviceSupport(new SonyHeadphonesSupport());
|
||||
case SONY_WF_1000XM4:
|
||||
return new ServiceDeviceSupport(new SonyHeadphonesSupport());
|
||||
case VESC_NRF:
|
||||
case VESC_HM10:
|
||||
return new ServiceDeviceSupport(new VescDeviceSupport(device.getType()));
|
||||
|
@ -41,6 +41,9 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
|
||||
private final SonyHeadphonesProtocol mProtocol;
|
||||
|
||||
private final UUID btrfcommUuidV1 = UUID.fromString("96CC203E-5068-46ad-B32D-E316F5E069BA");
|
||||
private final UUID btrfcommUuidV2 = UUID.fromString("956C7B26-D49A-4BA8-B03F-B17D393CB6E2");
|
||||
|
||||
// Track whether we got the first init reply
|
||||
private final Handler handler = new Handler();
|
||||
private int initRetries = 0;
|
||||
@ -67,7 +70,11 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
}
|
||||
};
|
||||
|
||||
public SonyHeadphonesIoThread(GBDevice gbDevice, Context context, SonyHeadphonesProtocol protocol, SonyHeadphonesSupport support, BluetoothAdapter btAdapter) {
|
||||
public SonyHeadphonesIoThread(final GBDevice gbDevice,
|
||||
final Context context,
|
||||
final SonyHeadphonesProtocol protocol,
|
||||
final SonyHeadphonesSupport support,
|
||||
final BluetoothAdapter btAdapter) {
|
||||
super(gbDevice, context, protocol, support, btAdapter);
|
||||
mProtocol = protocol;
|
||||
}
|
||||
@ -80,7 +87,7 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
public synchronized void write(final byte[] bytes) {
|
||||
// Log the human-readable message, for debugging
|
||||
LOG.info("Writing {}", Message.fromBytes(bytes));
|
||||
|
||||
@ -88,7 +95,7 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] parseIncoming(InputStream inputStream) throws IOException {
|
||||
protected byte[] parseIncoming(final InputStream inputStream) throws IOException {
|
||||
final ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||
final byte[] incoming = new byte[1];
|
||||
|
||||
@ -109,8 +116,22 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
|
||||
return UUID.fromString("96CC203E-5068-46ad-B32D-E316F5E069BA");
|
||||
protected UUID getUuidToConnect(@NonNull final ParcelUuid[] uuids) {
|
||||
boolean hasV2 = false;
|
||||
for (final ParcelUuid uuid : uuids) {
|
||||
if (uuid.getUuid().equals(btrfcommUuidV1)) {
|
||||
// Prioritize V1
|
||||
return btrfcommUuidV1;
|
||||
} else if (uuid.getUuid().equals(btrfcommUuidV2)) {
|
||||
hasV2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasV2) {
|
||||
return btrfcommUuidV2;
|
||||
}
|
||||
|
||||
return super.getUuidToConnect(uuids);
|
||||
}
|
||||
|
||||
private void scheduleInitRetry() {
|
||||
|
@ -48,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.prot
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2.SonyProtocolImplV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
|
||||
public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
@ -101,9 +102,8 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
// Init reply, set the protocol version
|
||||
if (message.getPayload().length == 4) {
|
||||
protocolImpl = new SonyProtocolImplV1(getDevice());
|
||||
} else if (message.getPayload().length == 6) {
|
||||
LOG.warn("Sony Headphones protocol v2 is not yet supported");
|
||||
return null;
|
||||
} else if (message.getPayload().length == 8) {
|
||||
protocolImpl = new SonyProtocolImplV2(getDevice());
|
||||
} else {
|
||||
LOG.error("Unexpected init response payload length: {}", message.getPayload().length);
|
||||
return null;
|
||||
|
@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.pro
|
||||
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.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
@ -34,6 +35,7 @@ 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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
public abstract class AbstractSonyProtocolImpl {
|
||||
private final GBDevice device;
|
||||
@ -46,6 +48,10 @@ public abstract class AbstractSonyProtocolImpl {
|
||||
return this.device;
|
||||
}
|
||||
|
||||
protected SonyHeadphonesCoordinator getCoordinator() {
|
||||
return (SonyHeadphonesCoordinator) DeviceHelper.getInstance().getCoordinator(getDevice());
|
||||
}
|
||||
|
||||
public abstract Request getAmbientSoundControl();
|
||||
|
||||
public abstract Request setAmbientSoundControl(final AmbientSoundControl config);
|
||||
|
@ -512,7 +512,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
|
||||
put(SonyHeadphonesCapabilities.AutomaticPowerOffWhenTakenOff, getAutomaticPowerOff());
|
||||
put(SonyHeadphonesCapabilities.AutomaticPowerOffByTime, getAutomaticPowerOff());
|
||||
put(SonyHeadphonesCapabilities.TouchSensorSingle, getTouchSensor());
|
||||
put(SonyHeadphonesCapabilities.Equalizer, getEqualizer());
|
||||
put(SonyHeadphonesCapabilities.EqualizerSimple, getEqualizer());
|
||||
put(SonyHeadphonesCapabilities.EqualizerWithCustomBands, getEqualizer());
|
||||
put(SonyHeadphonesCapabilities.SoundPosition, getSoundPosition());
|
||||
put(SonyHeadphonesCapabilities.SurroundMode, getSurroundMode());
|
||||
put(SonyHeadphonesCapabilities.PauseWhenTakenOff, getPauseWhenTakenOff());
|
||||
|
@ -17,27 +17,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
|
||||
|
||||
public enum BatteryType {
|
||||
SINGLE(0x00),
|
||||
DUAL(0x01),
|
||||
CASE(0x02);
|
||||
|
||||
private final byte code;
|
||||
|
||||
BatteryType(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static BatteryType fromCode(final byte code) {
|
||||
for (final BatteryType batteryType : values()) {
|
||||
if (batteryType.code == code) {
|
||||
return batteryType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
SINGLE,
|
||||
DUAL,
|
||||
CASE,
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2022 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.service.devices.sony.headphones.protocol.impl.v2;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
|
||||
public enum PayloadTypeV2 {
|
||||
AUDIO_CODEC_REQUEST(MessageType.COMMAND_1, 0x13), // TODO confirm
|
||||
AUDIO_CODEC_REPLY(MessageType.COMMAND_1, 0x14), // TODO confirm
|
||||
AUDIO_CODEC_NOTIFY(MessageType.COMMAND_1, 0x15),
|
||||
|
||||
BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x22),
|
||||
BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x23),
|
||||
BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x24), // TODO confirm
|
||||
|
||||
AUTOMATIC_POWER_OFF_GET(MessageType.COMMAND_1, 0x26),
|
||||
AUTOMATIC_POWER_OFF_RET(MessageType.COMMAND_1, 0x27),
|
||||
AUTOMATIC_POWER_OFF_SET(MessageType.COMMAND_1, 0x28),
|
||||
AUTOMATIC_POWER_OFF_NOTIFY(MessageType.COMMAND_1, 0x29),
|
||||
|
||||
UNKNOWN(MessageType.UNKNOWN, 0xff);
|
||||
|
||||
private final MessageType messageType;
|
||||
private final byte code;
|
||||
|
||||
PayloadTypeV2(final MessageType messageType, final int code) {
|
||||
this.messageType = messageType;
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return this.messageType;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PayloadTypeV2 fromCode(final MessageType messageType, final byte code) {
|
||||
for (final PayloadTypeV2 payloadType : values()) {
|
||||
if (messageType.equals(payloadType.messageType) && payloadType.code == code) {
|
||||
return payloadType;
|
||||
}
|
||||
}
|
||||
|
||||
return PayloadTypeV2.UNKNOWN;
|
||||
}
|
||||
}
|
@ -0,0 +1,635 @@
|
||||
/* Copyright (C) 2022 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.service.devices.sony.headphones.protocol.impl.v2;
|
||||
|
||||
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;
|
||||
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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
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.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.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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.AudioCodec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonyProtocolImplV2.class);
|
||||
|
||||
public SonyProtocolImplV2(final GBDevice device) {
|
||||
super(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAmbientSoundControl() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getCode(),
|
||||
(byte) 0x15
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(8);
|
||||
|
||||
buf.put(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getCode());
|
||||
buf.put((byte) 0x15);
|
||||
buf.put((byte) 0x01); // 0x00 while dragging the slider?
|
||||
|
||||
if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x00);
|
||||
} else {
|
||||
buf.put((byte) 0x01);
|
||||
}
|
||||
|
||||
if (AmbientSoundControl.Mode.AMBIENT_SOUND.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x01);
|
||||
} else {
|
||||
buf.put((byte) 0x00);
|
||||
}
|
||||
|
||||
if (AmbientSoundControl.Mode.WIND_NOISE_REDUCTION.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x03);
|
||||
} else {
|
||||
buf.put((byte) 0x02);
|
||||
}
|
||||
|
||||
buf.put((byte) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
|
||||
buf.put((byte) (ambientSoundControl.getAmbientSound()));
|
||||
|
||||
return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getNoiseCancellingOptimizerState() {
|
||||
LOG.warn("Noise cancelling optimizer not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAudioCodec() {
|
||||
return new Request(
|
||||
PayloadTypeV2.AUDIO_CODEC_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AUDIO_CODEC_REQUEST.getCode(),
|
||||
(byte) 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getBattery(final BatteryType batteryType) {
|
||||
return new Request(
|
||||
PayloadTypeV2.BATTERY_LEVEL_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.BATTERY_LEVEL_REQUEST.getCode(),
|
||||
encodeBatteryType(batteryType)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getFirmwareVersion() {
|
||||
return super.getFirmwareVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAudioUpsampling() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUDIO_UPSAMPLING_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUDIO_UPSAMPLING_GET.getCode(),
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAudioUpsampling(final AudioUpsampling config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUDIO_UPSAMPLING_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUDIO_UPSAMPLING_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x01 : 0x00)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAutomaticPowerOff() {
|
||||
return new Request(
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getCode(),
|
||||
(byte) 0x05
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAutomaticPowerOff(final AutomaticPowerOff config) {
|
||||
return new Request(
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_SET.getCode(),
|
||||
(byte) 0x05,
|
||||
config.getCode()[0],
|
||||
config.getCode()[1]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getButtonModes() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x03
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setButtonModes(final ButtonModes config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x03,
|
||||
(byte) 0x02,
|
||||
config.getModeLeft().getCode(),
|
||||
config.getModeRight().getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getPauseWhenTakenOff() {
|
||||
return new Request(
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AUTOMATIC_POWER_OFF_GET.getCode(),
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setPauseWhenTakenOff(final PauseWhenTakenOff config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x00 : 0x01) // this is reversed on V2...?
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getEqualizer() {
|
||||
return new Request(
|
||||
PayloadTypeV1.EQUALIZER_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.EQUALIZER_GET.getCode(),
|
||||
(byte) 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setEqualizerPreset(final EqualizerPreset config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.EQUALIZER_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.EQUALIZER_SET.getCode(),
|
||||
(byte) 0x00,
|
||||
config.getCode(),
|
||||
(byte) 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
|
||||
LOG.warn("Equalizer custom bands not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSoundPosition() {
|
||||
LOG.warn("Sound position not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSoundPosition(final SoundPosition config) {
|
||||
LOG.warn("Sound position not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSurroundMode() {
|
||||
LOG.warn("Surround mode not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSurroundMode(final SurroundMode config) {
|
||||
LOG.warn("Surround mode not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getTouchSensor() {
|
||||
LOG.warn("Touch sensor not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setTouchSensor(final TouchSensor config) {
|
||||
LOG.warn("Touch sensor not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getVoiceNotifications() {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setVoiceNotifications(final VoiceNotifications config) {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request startNoiseCancellingOptimizer(final boolean start) {
|
||||
LOG.warn("Noise cancelling optimizer not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request powerOff() {
|
||||
LOG.warn("Power off not implemented for V2");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
|
||||
final PayloadTypeV2 payloadType = PayloadTypeV2.fromCode(messageType, payload[0]);
|
||||
|
||||
switch (payloadType) {
|
||||
case AUDIO_CODEC_NOTIFY:
|
||||
return handleAudioCodec(payload);
|
||||
case BATTERY_LEVEL_NOTIFY:
|
||||
case BATTERY_LEVEL_REPLY:
|
||||
return handleBattery(payload);
|
||||
case AUTOMATIC_POWER_OFF_RET:
|
||||
case AUTOMATIC_POWER_OFF_NOTIFY:
|
||||
return handleAutomaticPowerOff(payload);
|
||||
}
|
||||
|
||||
return super.handlePayload(messageType, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleInitResponse(final byte[] payload) {
|
||||
return super.handleInitResponse(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
|
||||
if (payload.length != 8) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x15) {
|
||||
LOG.warn("Not ambient sound control, ignoring {}", payload[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
AmbientSoundControl.Mode mode = null;
|
||||
|
||||
if (payload[3] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.OFF;
|
||||
} else if (payload[3] == (byte) 0x01) {
|
||||
// Enabled, determine mode
|
||||
|
||||
if (payload[5] == 0x03 || payload[5] == 0x05) {
|
||||
mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
|
||||
} else if (payload[5] == 0x02) {
|
||||
if (payload[4] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
|
||||
} else if (payload[4] == (byte) 0x01) {
|
||||
mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unable to determine ambient sound control mode from {}", GB.hexdump(payload));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean focusOnVoice = booleanFromByte(payload[6]);
|
||||
if (focusOnVoice == null) {
|
||||
LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[6]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int ambientSound = payload[7];
|
||||
if (ambientSound < 0 || ambientSound > 20) {
|
||||
LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[7]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AmbientSoundControl ambientSoundControl = new AmbientSoundControl(mode, focusOnVoice, ambientSound);
|
||||
|
||||
LOG.warn("Ambient sound control: {}", ambientSoundControl);
|
||||
|
||||
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(ambientSoundControl.toPreferences());
|
||||
|
||||
return Collections.singletonList(eventUpdatePreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerStatus(final byte[] payload) {
|
||||
LOG.warn("Touch sensor not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleNoiseCancellingOptimizerState(final byte[] payload) {
|
||||
LOG.warn("Touch sensor not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAudioUpsampling(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x01) {
|
||||
LOG.warn("Not audio upsampling, ignoring {}", payload[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean enabled = booleanFromByte(payload[2]);
|
||||
if (enabled == null) {
|
||||
LOG.warn("Unknown audio upsampling code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Audio Upsampling: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new AudioUpsampling(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAutomaticPowerOff(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x05) {
|
||||
LOG.warn("Not automatic power off config, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AutomaticPowerOff mode = AutomaticPowerOff.fromCode(payload[2], payload[3]);
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown automatic power off codes {}", String.format("%02x %02x", payload[3], payload[4]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Automatic Power Off: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleButtonModes(final byte[] payload) {
|
||||
if (payload.length != 5) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x03) {
|
||||
LOG.warn("Not button mode config, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final ButtonModes.Mode modeLeft = ButtonModes.Mode.fromCode(payload[3]);
|
||||
final ButtonModes.Mode modeRight = ButtonModes.Mode.fromCode(payload[4]);
|
||||
|
||||
if (modeLeft == null || modeRight == null) {
|
||||
LOG.warn("Unknown button mode codes {}", String.format("%02x %02x", payload[3], payload[4]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Button Modes: L: {}, R: {}", modeLeft, modeRight);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new ButtonModes(modeLeft, modeRight).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handlePauseWhenTakenOff(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x01) {
|
||||
LOG.warn("Not pause when taken off, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean disabled = booleanFromByte(payload[2]);
|
||||
if (disabled == null) {
|
||||
LOG.warn("Unknown pause when taken off code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Pause when taken off: {}", !disabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new PauseWhenTakenOff(!disabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleBattery(final byte[] payload) {
|
||||
return super.handleBattery(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAudioCodec(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x03) {
|
||||
LOG.warn("Not audio codec, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AudioCodec audioCodec = AudioCodec.fromCode(payload[2]);
|
||||
if (audioCodec == null) {
|
||||
LOG.warn("Unable to determine audio codec from {}", GB.hexdump(payload));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("AUDIO_CODEC: ", audioCodec.name());
|
||||
|
||||
final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
|
||||
.withPreference(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_CODEC, audioCodec.name().toLowerCase(Locale.getDefault()));
|
||||
|
||||
return Arrays.asList(gbDeviceEventUpdateDeviceInfo, gbDeviceEventUpdatePreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleEqualizer(final byte[] payload) {
|
||||
return super.handleEqualizer(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleFirmwareVersion(final byte[] payload) {
|
||||
return super.handleFirmwareVersion(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleJson(final byte[] payload) {
|
||||
LOG.warn("JSON not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAutomaticPowerOffButtonMode(final byte[] payload) {
|
||||
switch (payload[1]) {
|
||||
case 0x01:
|
||||
return handlePauseWhenTakenOff(payload);
|
||||
case 0x03:
|
||||
return handleButtonModes(payload);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleVirtualSound(final byte[] payload) {
|
||||
LOG.warn("Virtual sound not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleSoundPosition(final byte[] payload) {
|
||||
LOG.warn("Sound position not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleSurroundMode(final byte[] payload) {
|
||||
LOG.warn("Surround mode not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleTouchSensor(final byte[] payload) {
|
||||
LOG.warn("Touch sensor not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatteryType decodeBatteryType(final byte b) {
|
||||
switch (b) {
|
||||
case 0x09:
|
||||
return BatteryType.DUAL;
|
||||
case 0x0a:
|
||||
return BatteryType.CASE;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte encodeBatteryType(final BatteryType batteryType) {
|
||||
switch (batteryType) {
|
||||
case DUAL:
|
||||
return 0x09;
|
||||
case CASE:
|
||||
case SINGLE: // TODO: This is not the code for single, but we need to encode something
|
||||
return 0x0a;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown battery type " + batteryType);
|
||||
}
|
||||
}
|
@ -129,6 +129,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinat
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWFSP800NCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -341,6 +342,7 @@ public class DeviceHelper {
|
||||
result.add(new SonyWFSP800NCoordinator());
|
||||
result.add(new SonyWF1000XM3Coordinator());
|
||||
result.add(new SonyWH1000XM2Coordinator());
|
||||
result.add(new SonyWF1000XM4Coordinator());
|
||||
result.add(new QC35Coordinator());
|
||||
result.add(new BinarySensorCoordinator());
|
||||
result.add(new FlipperZeroCoordinator());
|
||||
|
@ -1149,6 +1149,7 @@
|
||||
<string name="devicetype_sony_wh_1000xm4">Sony WH-1000XM4</string>
|
||||
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
|
||||
<string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
|
||||
<string name="devicetype_sony_wf_1000xm4">Sony WF-1000XM4</string>
|
||||
<string name="devicetype_binary_sensor">Binary sensor</string>
|
||||
<string name="choose_auto_export_location">Choose export location</string>
|
||||
<string name="notification_channel_name">General</string>
|
||||
|
@ -1,58 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceScreen
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_equalizer_mode_names"
|
||||
android:entryValues="@array/sony_equalizer_mode_values"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_equalizer">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_equalizer_mode_names"
|
||||
android:entryValues="@array/sony_equalizer_mode_values"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_equalizer" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceScreen>
|
||||
|
||||
android:key="pref_sony_equalizer_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_equalizer" />
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_equalizer">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_equalizer_mode_names"
|
||||
android:entryValues="@array/sony_equalizer_mode_values"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_equalizer" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceScreen>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
@ -0,0 +1,54 @@
|
||||
/* Copyright (C) 2022 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.service.devices.sony.headphones.protocol.impl;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyTestUtils {
|
||||
public static void assertRequest(final Request request, final String messageHex) {
|
||||
final Message message = Message.fromBytes(GB.hexStringToByteArray(messageHex.replace(":", "")));
|
||||
assertRequest(request, message.getType(), message.getPayload());
|
||||
}
|
||||
|
||||
public static void assertRequest(final Request request, final int messageType, final String payloadHex) {
|
||||
assertRequest(
|
||||
request,
|
||||
MessageType.fromCode((byte) messageType),
|
||||
GB.hexStringToByteArray(payloadHex.replace(":", ""))
|
||||
);
|
||||
}
|
||||
|
||||
public static void assertRequest(final Request request, final MessageType messageType, final byte[] payload) {
|
||||
assertEquals("Message types should be the same", messageType, request.messageType());
|
||||
assertArrayEquals("Payloads should be the same", payload, request.payload());
|
||||
}
|
||||
|
||||
public static List<? extends GBDeviceEvent> handleMessage(final AbstractSonyProtocolImpl protocol, final String messageHex) {
|
||||
final Message message = Message.fromBytes(GB.hexStringToByteArray(messageHex.replace(":", "")));
|
||||
|
||||
return protocol.handlePayload(message.getType(), message.getPayload());
|
||||
}
|
||||
}
|
@ -0,0 +1,300 @@
|
||||
/* Copyright (C) 2022 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.service.devices.sony.headphones.protocol.impl.v1;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyProtocolImplV1Test {
|
||||
private final SonyProtocolImplV1 protocol = new SonyProtocolImplV1(null);
|
||||
|
||||
@Test
|
||||
public void getAmbientSoundControl() {
|
||||
// TODO
|
||||
final Request request = protocol.getAmbientSoundControl();
|
||||
assertRequest(request, 0x0c, "66:02");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAmbientSoundControl() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNoiseCancellingOptimizerState() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAudioCodec() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBattery() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirmwareVersion() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAudioUpsampling() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAudioUpsampling() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getButtonModes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setButtonModes() {
|
||||
// TODO
|
||||
final Request request = protocol.setButtonModes(new ButtonModes(
|
||||
ButtonModes.Mode.AMBIENT_SOUND_CONTROL,
|
||||
ButtonModes.Mode.PLAYBACK_CONTROL
|
||||
));
|
||||
assertRequest(request, "3e0c0100000005f806020020323c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPauseWhenTakenOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPauseWhenTakenOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEqualizer() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEqualizerPreset() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEqualizerCustomBands() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSoundPosition() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSoundPosition() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSurroundMode() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSurroundMode() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTouchSensor() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTouchSensor() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getVoiceNotifications() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVoiceNotifications() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startNoiseCancellingOptimizer() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void powerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlePayload() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validInitPayload() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleInitResponse() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAmbientSoundControl() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleNoiseCancellingOptimizerStatus() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleNoiseCancellingOptimizerState() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAudioUpsampling() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleButtonModes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlePauseWhenTakenOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleBattery() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAudioCodec() {
|
||||
// TODO
|
||||
final List<? extends GBDeviceEvent> event = protocol.handlePayload(
|
||||
MessageType.fromCode((byte) 0x0c),
|
||||
GB.hexStringToByteArray("1b:00:01".replace(":", ""))
|
||||
);
|
||||
|
||||
assertEquals("Expect 2 events", 2, event.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleEqualizer() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleFirmwareVersion() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleJson() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAutomaticPowerOffButtonMode() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleVirtualSound() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleSoundPosition() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleSurroundMode() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleTouchSensor() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleVoiceNotifications() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void booleanFromByte() {
|
||||
assertEquals(Boolean.FALSE, protocol.booleanFromByte((byte) 0x00));
|
||||
assertEquals(Boolean.TRUE, protocol.booleanFromByte((byte) 0x01));
|
||||
assertNull(protocol.booleanFromByte((byte) 0x02));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWindNoiseCancelling() {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
/* Copyright (C) 2022 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.service.devices.sony.headphones.protocol.impl.v2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.handleMessage;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
|
||||
public class SonyProtocolImplV2Test {
|
||||
private final SonyProtocolImplV2 protocol = new SonyProtocolImplV2(null) {
|
||||
@Override
|
||||
protected SonyHeadphonesCoordinator getCoordinator() {
|
||||
return new SonyWF1000XM4Coordinator();
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void getAmbientSoundControl() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAmbientSoundControl() {
|
||||
// TODO
|
||||
final Request request = protocol.setAmbientSoundControl(new AmbientSoundControl(
|
||||
AmbientSoundControl.Mode.WIND_NOISE_REDUCTION,
|
||||
true,
|
||||
15
|
||||
));
|
||||
assertRequest(request, "3e:0c:00:00:00:00:08:68:15:01:01:00:03:01:0f:a6:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNoiseCancellingOptimizerState() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAudioCodec() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBattery() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirmwareVersion() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAudioUpsampling() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAudioUpsampling() {
|
||||
final Request requestEnabled = protocol.setAudioUpsampling(new AudioUpsampling(true));
|
||||
assertRequest(requestEnabled, "3e:0c:00:00:00:00:03:e8:01:01:f9:3c");
|
||||
|
||||
final Request requestDisabled = protocol.setAudioUpsampling(new AudioUpsampling(false));
|
||||
assertRequest(requestDisabled, "3e:0c:01:00:00:00:03:e8:01:00:f9:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getButtonModes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setButtonModes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPauseWhenTakenOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPauseWhenTakenOff() {
|
||||
final Request requestEnabled = protocol.setPauseWhenTakenOff(new PauseWhenTakenOff(true));
|
||||
assertRequest(requestEnabled, "3e:0c:01:00:00:00:03:f8:01:00:09:3c");
|
||||
|
||||
final Request requestDisabled = protocol.setPauseWhenTakenOff(new PauseWhenTakenOff(false));
|
||||
assertRequest(requestDisabled, "3e:0c:00:00:00:00:03:f8:01:01:09:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEqualizer() {
|
||||
final Request requestDisabled = protocol.getEqualizer();
|
||||
assertRequest(requestDisabled, "3e:0c:00:00:00:00:02:56:00:64:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEqualizerPreset() {
|
||||
final Map<EqualizerPreset, String> commands = new LinkedHashMap<EqualizerPreset, String>() {{
|
||||
put(EqualizerPreset.OFF, "3e:0c:01:00:00:00:04:58:00:00:00:69:3c");
|
||||
put(EqualizerPreset.BRIGHT, "3e:0c:01:00:00:00:04:58:00:10:00:79:3c");
|
||||
put(EqualizerPreset.EXCITED, "3e:0c:01:00:00:00:04:58:00:11:00:7a:3c");
|
||||
put(EqualizerPreset.MELLOW, "3e:0c:01:00:00:00:04:58:00:12:00:7b:3c");
|
||||
put(EqualizerPreset.RELAXED, "3e:0c:01:00:00:00:04:58:00:13:00:7c:3c");
|
||||
put(EqualizerPreset.VOCAL, "3e:0c:01:00:00:00:04:58:00:14:00:7d:3c");
|
||||
put(EqualizerPreset.TREBLE_BOOST, "3e:0c:01:00:00:00:04:58:00:15:00:7e:3c");
|
||||
put(EqualizerPreset.BASS_BOOST, "3e:0c:01:00:00:00:04:58:00:16:00:7f:3c");
|
||||
put(EqualizerPreset.SPEECH, "3e:0c:01:00:00:00:04:58:00:17:00:80:3c");
|
||||
put(EqualizerPreset.MANUAL, "3e:0c:01:00:00:00:04:58:00:a0:00:09:3c");
|
||||
put(EqualizerPreset.CUSTOM_1, "3e:0c:01:00:00:00:04:58:00:a1:00:0a:3c");
|
||||
put(EqualizerPreset.CUSTOM_2, "3e:0c:01:00:00:00:04:58:00:a2:00:0b:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<EqualizerPreset, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setEqualizerPreset(entry.getKey());
|
||||
assertRequest(request, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEqualizerCustomBands() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void getSoundPosition() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void setSoundPosition() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void getSurroundMode() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void setSurroundMode() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void getTouchSensor() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void setTouchSensor() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void getVoiceNotifications() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void setVoiceNotifications() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void startNoiseCancellingOptimizer() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void powerOff() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlePayload() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validInitPayload() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleInitResponse() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAmbientSoundControl() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleNoiseCancellingOptimizerStatus() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleNoiseCancellingOptimizerState() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAudioUpsampling() {
|
||||
final Map<AudioUpsampling, String> commands = new LinkedHashMap<AudioUpsampling, String>() {{
|
||||
put(new AudioUpsampling(false), "3e:0c:00:00:00:00:03:e9:01:00:f9:3c");
|
||||
put(new AudioUpsampling(true), "3e:0c:01:00:00:00:03:e9:01:01:fb:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AudioUpsampling, String> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
|
||||
assertEquals("Expect 1 events", 1, events.size());
|
||||
final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
|
||||
final Object modePrefValue = entry.getKey()
|
||||
.toPreferences()
|
||||
.get(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING);
|
||||
assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAutomaticPowerOff() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleButtonModes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlePauseWhenTakenOff() {
|
||||
final Map<PauseWhenTakenOff, String> commands = new LinkedHashMap<PauseWhenTakenOff, String>() {{
|
||||
put(new PauseWhenTakenOff(false), "3e:0c:00:00:00:00:03:f9:01:01:0a:3c");
|
||||
put(new PauseWhenTakenOff(true), "3e:0c:01:00:00:00:03:f9:01:00:0a:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<PauseWhenTakenOff, String> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
|
||||
assertEquals("Expect 1 events", 1, events.size());
|
||||
final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
|
||||
final Object modePrefValue = entry.getKey()
|
||||
.toPreferences()
|
||||
.get(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF);
|
||||
assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleBattery() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAudioCodec() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleEqualizer() {
|
||||
final Map<EqualizerPreset, String> commands = new LinkedHashMap<EqualizerPreset, String>() {{
|
||||
put(EqualizerPreset.OFF, "3e:0c:01:00:00:00:0a:59:00:00:06:0a:0a:0a:0a:0a:0a:b2:3c");
|
||||
put(EqualizerPreset.BRIGHT, "3e:0c:01:00:00:00:0a:59:00:10:06:09:0a:0f:11:11:13:dd:3c");
|
||||
put(EqualizerPreset.EXCITED, "3e:0c:01:00:00:00:0a:59:00:11:06:12:09:0b:0a:0d:0f:d3:3c");
|
||||
put(EqualizerPreset.MELLOW, "3e:0c:01:00:00:00:0a:59:00:12:06:07:09:08:07:06:04:b1:3c");
|
||||
put(EqualizerPreset.RELAXED, "3e:0c:01:00:00:00:0a:59:00:13:06:01:07:09:07:05:02:a8:3c");
|
||||
put(EqualizerPreset.VOCAL, "3e:0c:01:00:00:00:0a:59:00:14:06:0a:10:0e:0c:0d:09:d4:3c");
|
||||
put(EqualizerPreset.TREBLE_BOOST, "3e:0c:01:00:00:00:0a:59:00:15:06:0a:0a:0a:0c:10:14:d9:3c");
|
||||
put(EqualizerPreset.BASS_BOOST, "3e:0c:01:00:00:00:0a:59:00:16:06:11:0a:0a:0a:0a:0a:cf:3c");
|
||||
put(EqualizerPreset.SPEECH, "3e:0c:01:00:00:00:0a:59:00:17:06:00:0e:0d:0b:0c:00:bf:3c");
|
||||
put(EqualizerPreset.MANUAL, "3e:0c:01:00:00:00:0a:59:00:a0:06:0a:0a:0a:0a:0a:0a:52:3c");
|
||||
put(EqualizerPreset.CUSTOM_1, "3e:0c:01:00:00:00:0a:59:00:a1:06:0a:0a:0a:0a:0a:0a:53:3c");
|
||||
put(EqualizerPreset.CUSTOM_2, "3e:0c:01:00:00:00:0a:59:00:a2:06:0a:0a:0a:0a:0a:0a:54:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<EqualizerPreset, String> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
|
||||
assertEquals("Expect 1 events", 1, events.size());
|
||||
final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
|
||||
final Object modePrefValue = entry.getKey()
|
||||
.toPreferences()
|
||||
.get(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE);
|
||||
assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleFirmwareVersion() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleJson() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAutomaticPowerOffButtonMode() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleVirtualSound() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleSoundPosition() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleSurroundMode() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleTouchSensor() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleVoiceNotifications() {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user