diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index e7131c3b2..80fcf537a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -403,6 +403,22 @@ public class DeviceSettingsPreferenceConst { 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_MOONDROP_EQUALIZER_PRESET = "pref_moondrop_equalizer_preset"; + public static final String PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD = "pref_moondrop_touch_play_pause_earbud"; + public static final String PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER = "pref_moondrop_touch_play_pause_trigger"; + public static final String PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD = "pref_moondrop_touch_media_prev_earbud"; + public static final String PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER = "pref_moondrop_touch_media_prev_trigger"; + public static final String PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD = "pref_moondrop_touch_media_next_earbud"; + public static final String PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER = "pref_moondrop_touch_media_next_trigger"; + public static final String PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD = "pref_moondrop_touch_call_pick_hang_earbud"; + public static final String PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER = "pref_moondrop_touch_call_pick_hang_trigger"; + public static final String PREF_MOONDROP_TOUCH_CALL_START_EARBUD = "pref_moondrop_touch_call_start_earbud"; + public static final String PREF_MOONDROP_TOUCH_CALL_START_TRIGGER = "pref_moondrop_touch_call_start_trigger"; + public static final String PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD = "pref_moondrop_touch_assistant_earbud"; + public static final String PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER = "pref_moondrop_touch_assistant_trigger"; + public static final String PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD = "pref_moondrop_touch_anc_mode_earbud"; + public static final String PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER = "pref_moondrop_touch_anc_mode_trigger"; + public static final String PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level"; public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index bc829381e..070a495cd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -678,6 +678,22 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT); addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT); + addPreferenceHandlerFor(PREF_MOONDROP_EQUALIZER_PRESET); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_START_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_CALL_START_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD); + addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER); + addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE); addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java new file mode 100644 index 000000000..8456745a5 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/moondrop/MoondropSpaceTravelCoordinator.java @@ -0,0 +1,82 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.moondrop; + +import androidx.annotation.NonNull; + +import java.util.regex.Pattern; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop.MoondropSpaceTravelDeviceSupport; + +public class MoondropSpaceTravelCoordinator extends AbstractBLClassicDeviceCoordinator { + @Override + public String getManufacturer() { + return "Moondrop"; + } + + @Override + protected Pattern getSupportedDeviceName() { + return Pattern.compile("Moondrop Space Travel"); + } + + @Override + public int getDeviceNameResource() { + return R.string.devicetype_moondrop_space_travel; + } + + @Override + public int getBatteryCount() { + return 0; + } + + @Override + protected void deleteDevice( + @NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) + throws GBException {} + + @Override + public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { + final DeviceSpecificSettings settings = new DeviceSpecificSettings(); + + settings.addRootScreen(R.xml.devicesettings_moondrop_space_travel_equalizer); + settings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS); + settings.addSubScreen( + DeviceSpecificSettingsScreen.TOUCH_OPTIONS, + R.xml.devicesettings_moondrop_space_travel_touch); + settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS); + settings.addSubScreen( + DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, + R.xml.devicesettings_headphones); + + return settings; + } + + @NonNull + @Override + public Class getDeviceSupportClass() { + return MoondropSpaceTravelDeviceSupport.class; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index cd1b9a7c6..0c60d1f14 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -174,6 +174,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd02Coor import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd03Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaMhoC303Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.moondrop.MoondropSpaceTravelCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear2Coordinator; @@ -409,6 +410,7 @@ public enum DeviceType { SONY_WH_1000XM5(SonyWH1000XM5Coordinator.class), SONY_WF_1000XM5(SonyWF1000XM5Coordinator.class), SOUNDCORE_LIBERTY3_PRO(SoundcoreLiberty3ProCoordinator.class), + MOONDROP_SPACE_TRAVEL(MoondropSpaceTravelCoordinator.class), BOSE_QC35(QC35Coordinator.class), HONORBAND3(HonorBand3Coordinator.class), HONORBAND4(HonorBand4Coordinator.class), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java new file mode 100644 index 000000000..fc17e72ab --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/GaiaPacket.java @@ -0,0 +1,136 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; + +import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump; + +public class GaiaPacket { + private static final Logger LOG = LoggerFactory.getLogger(GaiaPacket.class); + + public static final byte PDU_COMMAND = (byte)0x00; + public static final byte PDU_NOTIFICATION = (byte)0x01; + public static final byte PDU_RESPONSE = (byte)0x02; + public static final byte PDU_ERROR = (byte)0x03; + + private static final int HEADER_LENGTH = 8; + private static final byte START_OF_PACKET = (byte)0xff; + private static final byte VERSION = (byte)0x04; + private static final byte FLAGS_NO_CHECK = (byte)0x00; + + // Qualcomm's vendor ID + private static final short VENDOR_ID = 0x001d; + + private byte featureId; + private byte pduType; + private byte pduId; + + private byte[] payload; + + public GaiaPacket(byte featureId, byte pduId) { + this(featureId, pduId, new byte[] {}); + } + + public GaiaPacket(byte featureId, byte pduId, byte[] payload) { + this(featureId, PDU_COMMAND, pduId, payload); + } + + private GaiaPacket(byte featureId, byte pduType, byte pduId, byte[] payload) { + LOG.debug( + "Packet: featureId={}, pduType={}, pduId={}, length={}", + String.format("0x%02x", featureId), + String.format("0x%02x", pduType), + String.format("0x%02x", pduId), + payload.length); + + this.featureId = featureId; + this.pduType = pduType; + this.pduId = pduId; + this.payload = payload; + } + + public byte getFeatureId() { + return featureId; + } + + public byte getPduType() { + return pduType; + } + + public byte getPduId() { + return pduId; + } + + public byte[] getPayload() { + return payload; + } + + public static GaiaPacket decode(ByteBuffer buf) { + if (buf.remaining() < HEADER_LENGTH) + return null; + + if (buf.get() != START_OF_PACKET) { + LOG.error("Invalid GAIA start of packet: {}", hexdump(buf.array())); + return null; + } + + if (buf.get() != VERSION) { + LOG.error("Invalid GAIA version: {}", hexdump(buf.array())); + return null; + } + + if (buf.get() != FLAGS_NO_CHECK) { + LOG.error("Invalid GAIA flags: {}", hexdump(buf.array())); + return null; + } + + byte length = buf.get(); + + if (buf.getShort() != VENDOR_ID) { + LOG.error("Invalid GAIA vendor ID: {}", hexdump(buf.array())); + return null; + } + + short commandId = buf.getShort(); + byte featureId = (byte)((commandId & 0xfe00) >> 9); + byte pduType = (byte)((commandId & 0x0180) >> 7); + byte pduId = (byte)(commandId & 0x7f); + + byte[] payload = new byte[length]; + buf.get(payload); + + return new GaiaPacket(featureId, pduType, pduId, payload); + } + + public byte[] encode() { + ByteBuffer buf = ByteBuffer.allocate(HEADER_LENGTH + payload.length); + + buf.put(START_OF_PACKET); + buf.put(VERSION); + buf.put(FLAGS_NO_CHECK); + buf.put((byte) payload.length); + buf.putShort(VENDOR_ID); + buf.putShort((short)((featureId << 9) | (pduType << 7) | pduId)); + buf.put(payload); + + return buf.array(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java new file mode 100644 index 000000000..db6dc6a2b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelDeviceSupport.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop; + +import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; + +public class MoondropSpaceTravelDeviceSupport extends AbstractHeadphoneDeviceSupport { + @Override + public boolean useAutoConnect() { + return false; + } + + @Override + protected GBDeviceProtocol createDeviceProtocol() { + return new MoondropSpaceTravelProtocol(getDevice()); + } + + @Override + protected GBDeviceIoThread createDeviceIOThread() { + return new MoondropSpaceTravelIOThread( + getDevice(), + getContext(), + (MoondropSpaceTravelProtocol)getDeviceProtocol(), + MoondropSpaceTravelDeviceSupport.this, + getBluetoothAdapter()); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java new file mode 100644 index 000000000..5f55e28a5 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelIOThread.java @@ -0,0 +1,64 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread; + +import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump; + +public class MoondropSpaceTravelIOThread extends BtClassicIoThread { + private static final Logger LOG = LoggerFactory.getLogger(MoondropSpaceTravelIOThread.class); + + private final MoondropSpaceTravelProtocol protocol; + + public MoondropSpaceTravelIOThread( + GBDevice device, + Context context, + MoondropSpaceTravelProtocol protocol, + MoondropSpaceTravelDeviceSupport support, + BluetoothAdapter adapter) { + super(device, context, protocol, support, adapter); + this.protocol = protocol; + } + + @Override + protected void initialize() { + write(protocol.encodeGetEqualizerPreset()); + write(protocol.encodeGetTouchActions()); + setUpdateState(GBDevice.State.INITIALIZED); + } + + @Override + protected byte[] parseIncoming(InputStream stream) throws IOException { + byte[] buffer = new byte[1048576]; + int bytes = stream.read(buffer); + LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes)); + + return Arrays.copyOf(buffer, bytes); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java new file mode 100644 index 000000000..7eba66e5a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/moondrop/MoondropSpaceTravelProtocol.java @@ -0,0 +1,187 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.moondrop; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; + +public class MoondropSpaceTravelProtocol extends GBDeviceProtocol { + private static final int EQUALIZER_PRESET_PKT_LEN = 1; + private static final byte EQUALIZER_PRESET_FEATURE = (byte)0x05; + private static final byte EQUALIZER_PRESET_PDU_GET = (byte)0x02; + private static final byte EQUALIZER_PRESET_PDU_SET = (byte)0x03; + + private static final int TOUCH_ACTIONS_PKT_LEN = 12; + private static final byte TOUCH_ACTIONS_FEATURE = (byte)0x01; + private static final byte TOUCH_ACTIONS_PDU_GET = (byte)0x02; + private static final byte TOUCH_ACTIONS_PDU_SET = (byte)0x03; + + protected MoondropSpaceTravelProtocol(GBDevice device) { + super(device); + } + + @Override + public GBDeviceEvent[] decodeResponse(byte[] data) { + List events = new ArrayList<>(); + ByteBuffer buf = ByteBuffer.wrap(data); + + while (buf.hasRemaining()) { + GaiaPacket packet = GaiaPacket.decode(buf); + + if (packet == null) + break; + + if (packet.getPduType() != GaiaPacket.PDU_RESPONSE) + continue; + + short featureId = packet.getFeatureId(); + short pduId = packet.getPduId(); + byte[] payload = packet.getPayload(); + + if (featureId == EQUALIZER_PRESET_FEATURE && pduId == EQUALIZER_PRESET_PDU_GET) + events.add(handlePacketEqualizerPreset(payload)); + else if (featureId == TOUCH_ACTIONS_FEATURE && pduId == TOUCH_ACTIONS_PDU_GET) + events.add(handlePacketTouchActions(payload)); + } + + return null; + } + + private GBDeviceEvent handlePacketEqualizerPreset(byte[] payload) { + if (payload.length != EQUALIZER_PRESET_PKT_LEN) + return null; + + byte preset = payload[0]; + + return new GBDeviceEventUpdatePreferences(PREF_MOONDROP_EQUALIZER_PRESET, String.valueOf(preset)); + } + + private GBDeviceEvent handlePacketTouchActions(byte[] payload) { + if (payload.length != TOUCH_ACTIONS_PKT_LEN) + return null; + + Map prefs = new HashMap<>(); + + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD, PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER, payload[1])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER, payload[2])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER, payload[3])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER, payload[6])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_CALL_START_EARBUD, PREF_MOONDROP_TOUCH_CALL_START_TRIGGER, payload[7])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD, PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER, payload[8])); + prefs.putAll(decodeTouchAction(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD, PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER, payload[10])); + + return new GBDeviceEventUpdatePreferences(prefs); + } + + @Override + public byte[] encodeSendConfiguration(String config) { + switch (config) { + case PREF_MOONDROP_EQUALIZER_PRESET: + return encodeSetEqualizerPreset(); + case PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD: + case PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER: + case PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD: + case PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER: + case PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD: + case PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER: + case PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD: + case PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER: + case PREF_MOONDROP_TOUCH_CALL_START_EARBUD: + case PREF_MOONDROP_TOUCH_CALL_START_TRIGGER: + case PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD: + case PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER: + case PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD: + case PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER: + return encodeSetTouchActions(); + } + + return super.encodeSendConfiguration(config); + } + + public byte[] encodeGetEqualizerPreset() { + return new GaiaPacket(EQUALIZER_PRESET_FEATURE, EQUALIZER_PRESET_PDU_GET).encode(); + } + + public byte[] encodeGetTouchActions() { + return new GaiaPacket(TOUCH_ACTIONS_FEATURE, TOUCH_ACTIONS_PDU_GET).encode(); + } + + private byte[] encodeSetEqualizerPreset() { + Prefs prefs = getDevicePrefs(); + byte preset = Byte.parseByte(prefs.getString(PREF_MOONDROP_EQUALIZER_PRESET, "0")); + + byte[] payload = new byte[] { preset }; + + return new GaiaPacket(EQUALIZER_PRESET_FEATURE, EQUALIZER_PRESET_PDU_SET, payload).encode(); + } + + private byte[] encodeSetTouchActions() { + Prefs prefs = getDevicePrefs(); + byte actionPlayPause = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD, PREF_MOONDROP_TOUCH_PLAY_PAUSE_TRIGGER); + byte actionMediaPrev = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_MEDIA_PREV_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_PREV_TRIGGER); + byte actionMediaNext = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_MEDIA_NEXT_EARBUD, PREF_MOONDROP_TOUCH_MEDIA_NEXT_TRIGGER); + byte actionCallPickHang = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_EARBUD, PREF_MOONDROP_TOUCH_CALL_PICK_HANG_TRIGGER); + byte actionCallStart = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_CALL_START_EARBUD, PREF_MOONDROP_TOUCH_CALL_START_TRIGGER); + byte actionAssistant = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_ASSISTANT_EARBUD, PREF_MOONDROP_TOUCH_ASSISTANT_TRIGGER); + byte actionAncMode = encodeTouchAction(prefs, PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD, PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER); + + byte[] payload = new byte[] { + (byte)0x01, + actionPlayPause, + actionMediaPrev, + actionMediaNext, + (byte)0x30, + (byte)0x30, + actionCallPickHang, + actionCallStart, + actionAssistant, + actionAssistant, + actionAncMode, + actionAncMode, + }; + + return new GaiaPacket(TOUCH_ACTIONS_FEATURE, TOUCH_ACTIONS_PDU_SET, payload).encode(); + } + + private byte encodeTouchAction(Prefs prefs, String prefEarbud, String prefTrigger) { + int earbud = Integer.valueOf(prefs.getString(prefEarbud, "3")); + int trigger = Integer.valueOf(prefs.getString(prefTrigger, "0")); + + return (byte)((earbud << 4) | trigger); + } + + private Map decodeTouchAction(String prefEarbud, String prefTrigger, byte action) { + int earbud = (action & 0xf0) >> 4; + int trigger = action & 0x0f; + + return new HashMap() {{ + put(prefEarbud, String.valueOf(earbud)); + put(prefTrigger, String.valueOf(trigger)); + }}; + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 3229c9c26..bb93e03ad 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -3462,6 +3462,47 @@ AMBIENT_SOUND_CONTROL + + @string/moondrop_equalizer_preset_reference + @string/moondrop_equalizer_preset_basshead + @string/moondrop_equalizer_preset_monitor + + + + 0 + 1 + 2 + + + + @string/left + @string/right + @string/moondrop_touch_earbud_both + + + + 1 + 2 + 3 + + + + @string/pref_button_action_disabled + @string/single_tap + @string/double_tap + @string/triple_tap + @string/moondrop_touch_trigger_long_press_1s + @string/moondrop_touch_trigger_long_press_3s + + + + 0 + 1 + 2 + 3 + 4 + 5 + de.dennisguse.opentracks diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25294c7f6..dcc1c7466 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1574,6 +1574,7 @@ Sony LinkBuds Sony LinkBuds S Soundcore Liberty 3 Pro + Moondrop Space Travel Binary sensor Honor Band 3 Honor Band 4 @@ -2375,6 +2376,19 @@ Version 1 Version 2 Version 3 + Reference + Basshead + Monitor + Earbud + Both + Trigger + Play/Pause + Pick/Hang Call + Start Call + Trigger Voice Assistant + Switch Active Noise Cancelling Mode + Long Press (1s) + Long Press (3s) Protocol Version Auto Brightness Adjust screen brightness according to ambient light diff --git a/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml b/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml new file mode 100644 index 000000000..34e04bba6 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_moondrop_space_travel_equalizer.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml b/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml new file mode 100644 index 000000000..cceaab567 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_moondrop_space_travel_touch.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +