diff --git a/.idea/dictionaries/t.xml b/.idea/dictionaries/t.xml
index e2a60bb75..c82ca81bd 100644
--- a/.idea/dictionaries/t.xml
+++ b/.idea/dictionaries/t.xml
@@ -124,6 +124,7 @@
protomors
qhybrid
quallenauge
+ realme
rebelo
roidmi
romanization
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java
index 5546e24fd..937a8d832 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoEncoAirCoordinator.java
@@ -16,93 +16,60 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
-import androidx.annotation.NonNull;
+import android.util.Pair;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
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.DeviceSpecificSettingsCustomizer;
-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.model.BatteryConfig;
-import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.OppoHeadphonesSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
-public class OppoEncoAirCoordinator extends AbstractBLClassicDeviceCoordinator {
+public class OppoEncoAirCoordinator extends OppoHeadphonesCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("OPPO Enco Air", Pattern.LITERAL);
}
- @Override
- protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
-
- }
-
- @Override
- public String getManufacturer() {
- return "Oppo";
- }
-
- @NonNull
- @Override
- public Class extends DeviceSupport> getDeviceSupportClass() {
- return OppoHeadphonesSupport.class;
- }
-
@Override
public int getDeviceNameResource() {
return R.string.devicetype_oppo_enco_air;
}
- @Override
- public int getDefaultIconResource() {
- return R.drawable.ic_device_nothingear;
- }
-
- @Override
- public int getDisabledIconResource() {
- return R.drawable.ic_device_nothingear_disabled;
- }
-
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
- public int getBatteryCount() {
- return 3;
- }
+ protected Map, List> getTouchOptions() {
+ return new LinkedHashMap, List>() {{
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2), Arrays.asList(
+ TouchConfigValue.OFF,
+ TouchConfigValue.PLAY_PAUSE,
+ TouchConfigValue.PREVIOUS,
+ TouchConfigValue.NEXT,
+ TouchConfigValue.VOICE_ASSISTANT
+ ));
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3), Arrays.asList(
+ TouchConfigValue.OFF,
+ TouchConfigValue.VOICE_ASSISTANT,
+ TouchConfigValue.GAME_MODE
+ ));
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD), Arrays.asList(
+ TouchConfigValue.OFF,
+ TouchConfigValue.VOLUME_UP,
+ TouchConfigValue.VOLUME_DOWN
+ ));
- @Override
- public BatteryConfig[] getBatteryConfig(final GBDevice device) {
- final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
- final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
- final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
- return new BatteryConfig[]{battery1, battery2, battery3};
- }
-
- @Override
- public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
- final DeviceSpecificSettings settings = new DeviceSpecificSettings();
-
- settings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
- settings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_oppo_headphones_touch_options);
-
- settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
- settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones);
-
- return settings;
- }
-
- @Override
- public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
- return new OppoHeadphonesSettingsCustomizer();
+ // Right side is the same
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_2), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2)));
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_3), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3)));
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.HOLD), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD)));
+ }};
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java
new file mode 100644
index 000000000..a05297dc7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesCoordinator.java
@@ -0,0 +1,101 @@
+/* Copyright (C) 2024 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
+
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+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.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.OppoHeadphonesSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+
+public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceCoordinator {
+ @Override
+ protected void deleteDevice(@NonNull final GBDevice gbDevice, @NonNull final Device device, @NonNull final DaoSession session) throws GBException {
+
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Oppo";
+ }
+
+ @NonNull
+ @Override
+ public Class extends DeviceSupport> getDeviceSupportClass() {
+ return OppoHeadphonesSupport.class;
+ }
+
+ @Override
+ public int getDefaultIconResource() {
+ return R.drawable.ic_device_nothingear;
+ }
+
+ @Override
+ public int getDisabledIconResource() {
+ return R.drawable.ic_device_nothingear_disabled;
+ }
+
+ @Override
+ public int getBatteryCount() {
+ return 3;
+ }
+
+ @Override
+ public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+ final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+ final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+ final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_tws_case, R.string.battery_case);
+ return new BatteryConfig[]{battery1, battery2, battery3};
+ }
+
+ @Override
+ public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+ final DeviceSpecificSettings settings = new DeviceSpecificSettings();
+
+ settings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
+ settings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_oppo_headphones_touch_options);
+
+ settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+ settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones);
+
+ return settings;
+ }
+
+ @Override
+ public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+ return new OppoHeadphonesSettingsCustomizer(getTouchOptions());
+ }
+
+ protected abstract Map, List> getTouchOptions();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java
index 693a356e7..c081027a1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/oppo/OppoHeadphonesSettingsCustomizer.java
@@ -17,23 +17,43 @@
package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
import android.os.Parcel;
+import android.util.Pair;
+import androidx.preference.ListPreference;
import androidx.preference.Preference;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+ private final Map, List> touchOptions;
+
public static final Creator CREATOR = new Creator() {
@Override
public OppoHeadphonesSettingsCustomizer createFromParcel(final Parcel in) {
- return new OppoHeadphonesSettingsCustomizer();
+ final Map, List> touchOptions = new LinkedHashMap<>();
+ final int numOptions = in.readInt();
+ for (int i = 0; i < numOptions; i++) {
+ final TouchConfigSide touchConfigSide = TouchConfigSide.valueOf(in.readString());
+ final TouchConfigType touchConfigType = TouchConfigType.valueOf(in.readString());
+ final List values = new ArrayList<>();
+ in.readList(values, TouchConfigValue.class.getClassLoader());
+ touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values);
+ }
+ return new OppoHeadphonesSettingsCustomizer(touchOptions);
}
@Override
@@ -42,15 +62,70 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
}
};
+ public OppoHeadphonesSettingsCustomizer(final Map, List> touchOptions) {
+ this.touchOptions = touchOptions;
+ }
+
@Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
+ final Set knownSides = new HashSet<>();
+ final Set knownTypes = new HashSet<>();
+
+ for (final Map.Entry, List> e : touchOptions.entrySet()) {
+ final TouchConfigSide side = e.getKey().first;
+ final TouchConfigType type = e.getKey().second;
+ final Set possibleValues = new HashSet<>(e.getValue());
+
+ knownSides.add(side);
+ knownTypes.add(type);
+
+ final String key = OppoHeadphonesPreferences.getKey(side, type);
+ final ListPreference pref = handler.findPreference(key);
+ if (pref == null) {
+ continue;
+ }
+
+ final CharSequence[] originalEntries = pref.getEntries();
+ final CharSequence[] originalValues = pref.getEntryValues();
+ final CharSequence[] entries = new CharSequence[possibleValues.size()];
+ final CharSequence[] values = new CharSequence[possibleValues.size()];
+ int j = 0;
+ for (int i = 0; i < originalValues.length; i++) {
+ if (possibleValues.contains(TouchConfigValue.valueOf(originalValues[i].toString().toUpperCase(Locale.ROOT)))) {
+ entries[j] = originalEntries[i];
+ values[j] = originalValues[i];
+ j++;
+ }
+ }
+
+ pref.setEntries(entries);
+ pref.setEntryValues(values);
+
+ handler.addPreferenceHandlerFor(key);
+ }
+
for (final TouchConfigSide side : TouchConfigSide.values()) {
+ if (!knownSides.contains(side)) {
+ // Side not configurable, hide it completely
+ final Preference header = handler.findPreference("oppo_touch_header_" + side.name().toLowerCase(Locale.ROOT));
+ if (header != null) {
+ header.setVisible(false);
+ continue;
+ }
+ }
+
for (final TouchConfigType type : TouchConfigType.values()) {
- handler.addPreferenceHandlerFor(OppoHeadphonesPreferences.getKey(side, type));
+ if (!knownTypes.contains(type)) {
+ final String key = OppoHeadphonesPreferences.getKey(side, type);
+ final Preference pref = handler.findPreference(key);
+ if (pref != null) {
+ pref.setVisible(false);
+ }
+ }
}
}
}
@@ -67,5 +142,11 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
@Override
public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeInt(touchOptions.size());
+ for (final Map.Entry, List> e : touchOptions.entrySet()) {
+ dest.writeString(e.getKey().first.name());
+ dest.writeString(e.getKey().second.name());
+ dest.writeList(e.getValue());
+ }
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java
new file mode 100644
index 000000000..9ffea1b39
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/realme/RealmeBudsT110Coordinator.java
@@ -0,0 +1,73 @@
+/* Copyright (C) 2024 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.realme;
+
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoHeadphonesCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
+
+public class RealmeBudsT110Coordinator extends OppoHeadphonesCoordinator {
+ @Override
+ protected Pattern getSupportedDeviceName() {
+ return Pattern.compile("realme Buds T110", Pattern.LITERAL);
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Realme";
+ }
+
+ @Override
+ public int getDeviceNameResource() {
+ return R.string.devicetype_realme_buds_t110;
+ }
+
+ @Override
+ protected Map, List> getTouchOptions() {
+ return new LinkedHashMap, List>() {{
+ final List options = Arrays.asList(
+ TouchConfigValue.OFF,
+ TouchConfigValue.PLAY_PAUSE,
+ TouchConfigValue.PREVIOUS,
+ TouchConfigValue.NEXT,
+ TouchConfigValue.VOLUME_UP,
+ TouchConfigValue.VOLUME_DOWN,
+ TouchConfigValue.VOICE_ASSISTANT_REALME
+ );
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2), options);
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3), options);
+ put(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD), options);
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_2), options);
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_3), options);
+ put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.HOLD), options);
+ put(Pair.create(TouchConfigSide.BOTH, TouchConfigType.HOLD), Arrays.asList(
+ TouchConfigValue.OFF,
+ TouchConfigValue.GAME_MODE
+ ));
+ }};
+ }
+}
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 c97a90d52..d4ccfd5ae 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -231,6 +231,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT110Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.scannable.ScannableDeviceCoordinator;
@@ -544,6 +545,7 @@ public enum DeviceType {
SUPER_CARS(SuperCarsCoordinator.class),
ASTEROIDOS(AsteroidOSDeviceCoordinator.class),
OPPO_ENCO_AIR(OppoEncoAirCoordinator.class),
+ REALME_BUDS_T110(RealmeBudsT110Coordinator.class),
SOFLOW_SO6(SoFlowCoordinator.class),
WITHINGS_STEEL_HR(WithingsSteelHRDeviceCoordinator.class),
SONY_WENA_3(SonyWena3Coordinator.class),
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java
index 89d9c0cca..2dc87b72c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocol.java
@@ -66,7 +66,7 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
i++;
continue;
}
- final int totalLength = BLETypeConversions.toUint16(responseData, i + 1);
+ final int totalLength = responseData[i + 1] & 0xff;
if (responseData.length - i < totalLength + 2) {
LOG.error("Got partial response with {} bytes, expected {}", responseData.length - i, totalLength + 2);
break;
@@ -99,9 +99,9 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
}
final short zero = responseBuf.getShort();
- if (zero != 0) {
- LOG.error("Unexpected bytes: {}, expected 0", zero);
- return Collections.emptyList();
+ if (zero != 0 && zero != 4) {
+ // 0 on oppo, 4 on realme?
+ LOG.warn("Unexpected bytes: {}, expected 0 or 4", zero);
}
final short code = responseBuf.getShort();
@@ -146,10 +146,11 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
break;
}
- final String fwString = StringUtils.untilNullTerminator(payload, 1);
- if (fwString == null) {
- LOG.warn("Failed to get firmware string");
- break;
+ final String fwString;
+ if (payload[payload.length - 1] == 0) {
+ fwString = new String(ArrayUtils.subarray(payload, 2, payload.length - 1)).strip();
+ } else {
+ fwString = new String(ArrayUtils.subarray(payload, 2, payload.length - 2)).strip();
}
final String[] parts = fwString.split(",");
if (parts.length % 3 != 0) {
@@ -180,7 +181,18 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
}
}
- final String fwVersion = String.join(".", fwVersionParts);
+ final List nonNullParts = new ArrayList<>(fwVersionParts.length);
+ for (int i = 0; i < fwVersionParts.length; i++) {
+ if (fwVersionParts[i] == null) {
+ continue;
+ }
+ nonNullParts.add(fwVersionParts[i]);
+ if (fwVersionParts[i].contains(".")) {
+ // Realme devices have the version already with the dots, repeated multiple times
+ break;
+ }
+ }
+ final String fwVersion = String.join(".", nonNullParts);
final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
eventVersionInfo.fwVersion = fwVersion;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java
index 32c962b39..b717567b7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigSide.java
@@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
public enum TouchConfigSide {
LEFT(0x01),
RIGHT(0x02),
+ BOTH(0x04),
;
private final int code;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java
index ce49e1e46..ec496ec86 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/commands/TouchConfigValue.java
@@ -21,7 +21,8 @@ import androidx.annotation.Nullable;
public enum TouchConfigValue {
OFF(0x00),
PLAY_PAUSE(0x01),
- VOICE_ASSISTANT(0x03),
+ VOICE_ASSISTANT(0x03), // oppo
+ VOICE_ASSISTANT_REALME(0x04),
PREVIOUS(0x05),
NEXT(0x06),
GAME_MODE(0x11),
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 945ad0a8c..4c0cb8cef 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -3566,44 +3566,30 @@
- as_off
-
+
+
- @string/sony_button_mode_off
- @string/moondrop_touch_action_play_pause
- @string/pref_media_previous
- @string/pref_media_next
+ - @string/pref_media_volumeup
+ - @string/pref_media_volumedown
- @string/pref_title_touch_voice_assistant
-
-
-
- - off
- - play_pause
- - previous
- - next
- - voice_assistant
-
-
-
- - @string/sony_button_mode_off
- @string/pref_title_touch_voice_assistant
- @string/prefs_game_mode
-
- - off
- - voice_assistant
- - game_mode
-
-
-
- - @string/sony_button_mode_off
- - @string/pref_media_volumeup
- - @string/pref_media_volumedown
-
-
-
+
+
- off
+ - play_pause
+ - previous
+ - next
- volume_up
- volume_down
+ - voice_assistant
+ - voice_assistant_realme
+ - game_mode
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 14d798665..350de55c4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2489,6 +2489,7 @@
CMF Watch Pro
CMF Watch Pro 2
Oppo Enco Air
+ Realme Buds T110
Galaxy Buds
Galaxy Buds Live
Galaxy Buds Pro
diff --git a/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml
index 7c8c59b4f..bae22d44c 100644
--- a/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml
+++ b/app/src/main/res/xml/devicesettings_oppo_headphones_touch_options.xml
@@ -3,28 +3,29 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
+
+
+
+
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java
new file mode 100644
index 000000000..37133b85f
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/oppo/OppoHeadphonesProtocolTest.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.oppo;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class OppoHeadphonesProtocolTest extends TestBase {
+ @Test
+ public void testHandleFirmware() {
+ final OppoHeadphonesProtocol protocol = new OppoHeadphonesProtocol(null);
+ GBDeviceEvent[] oppoEvents = protocol.decodeResponse(GB.hexStringToByteArray("aa4f00000581f34800000a312c312c312c312c322c3136302c312c332c3838382c312c342c302c322c312c312c322c322c3136302c322c332c3838382c322c342c302c332c312c312c332c322c38323700"));
+ Assert.assertEquals(1, oppoEvents.length);
+ GBDeviceEventVersionInfo oppoEvent = (GBDeviceEventVersionInfo) oppoEvents[0];
+ Assert.assertEquals("160.160.827", oppoEvent.fwVersion);
+
+ GBDeviceEvent[] realme = protocol.decodeResponse(GB.hexStringToByteArray("aa2a040005810e23000003312c322c312e312e302e37352c322c322c312e312e302e37352c332c322c303031"));
+ Assert.assertEquals(1, realme.length);
+ GBDeviceEventVersionInfo realmeEvent = (GBDeviceEventVersionInfo) realme[0];
+ Assert.assertEquals("1.1.0.75", realmeEvent.fwVersion);
+ }
+}