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 extends DeviceSupport> 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+