1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 04:16:49 +01:00

Realme Buds T110: Initial support

This commit is contained in:
José Rebelo 2024-11-17 17:23:23 +00:00
parent 1618fda418
commit 5f91715c89
13 changed files with 391 additions and 122 deletions

View File

@ -124,6 +124,7 @@
<w>protomors</w> <w>protomors</w>
<w>qhybrid</w> <w>qhybrid</w>
<w>quallenauge</w> <w>quallenauge</w>
<w>realme</w>
<w>rebelo</w> <w>rebelo</w>
<w>roidmi</w> <w>roidmi</w>
<w>romanization</w> <w>romanization</w>

View File

@ -16,93 +16,60 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.oppo; 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 java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
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;
public class OppoEncoAirCoordinator extends AbstractBLClassicDeviceCoordinator { public class OppoEncoAirCoordinator extends OppoHeadphonesCoordinator {
@Override @Override
protected Pattern getSupportedDeviceName() { protected Pattern getSupportedDeviceName() {
return Pattern.compile("OPPO Enco Air", Pattern.LITERAL); 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 @Override
public int getDeviceNameResource() { public int getDeviceNameResource() {
return R.string.devicetype_oppo_enco_air; 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 @Override
public boolean supportsFindDevice() { public boolean supportsFindDevice() {
return true; return true;
} }
@Override @Override
public int getBatteryCount() { protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
return 3; return new LinkedHashMap<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>>() {{
} 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 // Right side is the same
public BatteryConfig[] getBatteryConfig(final GBDevice device) { put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_2), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_2)));
final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud); put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.TAP_3), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.TAP_3)));
final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud); put(Pair.create(TouchConfigSide.RIGHT, TouchConfigType.HOLD), get(Pair.create(TouchConfigSide.LEFT, TouchConfigType.HOLD)));
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();
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions();
}

View File

@ -17,23 +17,43 @@
package nodomain.freeyourgadget.gadgetbridge.devices.oppo; package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
import android.os.Parcel; import android.os.Parcel;
import android.util.Pair;
import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import java.util.ArrayList;
import java.util.Collections; 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 java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigSide; 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.TouchConfigType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigValue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer { public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
private final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions;
public static final Creator<OppoHeadphonesSettingsCustomizer> CREATOR = new Creator<OppoHeadphonesSettingsCustomizer>() { public static final Creator<OppoHeadphonesSettingsCustomizer> CREATOR = new Creator<OppoHeadphonesSettingsCustomizer>() {
@Override @Override
public OppoHeadphonesSettingsCustomizer createFromParcel(final Parcel in) { public OppoHeadphonesSettingsCustomizer createFromParcel(final Parcel in) {
return new OppoHeadphonesSettingsCustomizer(); final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> 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<TouchConfigValue> values = new ArrayList<>();
in.readList(values, TouchConfigValue.class.getClassLoader());
touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values);
}
return new OppoHeadphonesSettingsCustomizer(touchOptions);
} }
@Override @Override
@ -42,15 +62,70 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
} }
}; };
public OppoHeadphonesSettingsCustomizer(final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions) {
this.touchOptions = touchOptions;
}
@Override @Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) { public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
} }
@Override @Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) { public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
final Set<TouchConfigSide> knownSides = new HashSet<>();
final Set<TouchConfigType> knownTypes = new HashSet<>();
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
final TouchConfigSide side = e.getKey().first;
final TouchConfigType type = e.getKey().second;
final Set<TouchConfigValue> 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()) { 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()) { 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 @Override
public void writeToParcel(final Parcel dest, final int flags) { public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(touchOptions.size());
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
dest.writeString(e.getKey().first.name());
dest.writeString(e.getKey().second.name());
dest.writeList(e.getValue());
}
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
return new LinkedHashMap<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>>() {{
final List<TouchConfigValue> 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
));
}};
}
}

View File

@ -231,6 +231,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator; 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.Roidmi1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.scannable.ScannableDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.scannable.ScannableDeviceCoordinator;
@ -544,6 +545,7 @@ public enum DeviceType {
SUPER_CARS(SuperCarsCoordinator.class), SUPER_CARS(SuperCarsCoordinator.class),
ASTEROIDOS(AsteroidOSDeviceCoordinator.class), ASTEROIDOS(AsteroidOSDeviceCoordinator.class),
OPPO_ENCO_AIR(OppoEncoAirCoordinator.class), OPPO_ENCO_AIR(OppoEncoAirCoordinator.class),
REALME_BUDS_T110(RealmeBudsT110Coordinator.class),
SOFLOW_SO6(SoFlowCoordinator.class), SOFLOW_SO6(SoFlowCoordinator.class),
WITHINGS_STEEL_HR(WithingsSteelHRDeviceCoordinator.class), WITHINGS_STEEL_HR(WithingsSteelHRDeviceCoordinator.class),
SONY_WENA_3(SonyWena3Coordinator.class), SONY_WENA_3(SonyWena3Coordinator.class),

View File

@ -66,7 +66,7 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
i++; i++;
continue; continue;
} }
final int totalLength = BLETypeConversions.toUint16(responseData, i + 1); final int totalLength = responseData[i + 1] & 0xff;
if (responseData.length - i < totalLength + 2) { if (responseData.length - i < totalLength + 2) {
LOG.error("Got partial response with {} bytes, expected {}", responseData.length - i, totalLength + 2); LOG.error("Got partial response with {} bytes, expected {}", responseData.length - i, totalLength + 2);
break; break;
@ -99,9 +99,9 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
} }
final short zero = responseBuf.getShort(); final short zero = responseBuf.getShort();
if (zero != 0) { if (zero != 0 && zero != 4) {
LOG.error("Unexpected bytes: {}, expected 0", zero); // 0 on oppo, 4 on realme?
return Collections.emptyList(); LOG.warn("Unexpected bytes: {}, expected 0 or 4", zero);
} }
final short code = responseBuf.getShort(); final short code = responseBuf.getShort();
@ -146,10 +146,11 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
break; break;
} }
final String fwString = StringUtils.untilNullTerminator(payload, 1); final String fwString;
if (fwString == null) { if (payload[payload.length - 1] == 0) {
LOG.warn("Failed to get firmware string"); fwString = new String(ArrayUtils.subarray(payload, 2, payload.length - 1)).strip();
break; } else {
fwString = new String(ArrayUtils.subarray(payload, 2, payload.length - 2)).strip();
} }
final String[] parts = fwString.split(","); final String[] parts = fwString.split(",");
if (parts.length % 3 != 0) { if (parts.length % 3 != 0) {
@ -180,7 +181,18 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
} }
} }
final String fwVersion = String.join(".", fwVersionParts); final List<String> 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(); final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
eventVersionInfo.fwVersion = fwVersion; eventVersionInfo.fwVersion = fwVersion;

View File

@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
public enum TouchConfigSide { public enum TouchConfigSide {
LEFT(0x01), LEFT(0x01),
RIGHT(0x02), RIGHT(0x02),
BOTH(0x04),
; ;
private final int code; private final int code;

View File

@ -21,7 +21,8 @@ import androidx.annotation.Nullable;
public enum TouchConfigValue { public enum TouchConfigValue {
OFF(0x00), OFF(0x00),
PLAY_PAUSE(0x01), PLAY_PAUSE(0x01),
VOICE_ASSISTANT(0x03), VOICE_ASSISTANT(0x03), // oppo
VOICE_ASSISTANT_REALME(0x04),
PREVIOUS(0x05), PREVIOUS(0x05),
NEXT(0x06), NEXT(0x06),
GAME_MODE(0x11), GAME_MODE(0x11),

View File

@ -3566,44 +3566,30 @@
<item>as_off</item> <item>as_off</item>
</string-array> </string-array>
<string-array name="oppo_touch_tap_2_names"> <string-array name="oppo_touch_tap_names">
<!-- superset of values, unsupported are hidden by the coordinator -->
<item>@string/sony_button_mode_off</item> <item>@string/sony_button_mode_off</item>
<item>@string/moondrop_touch_action_play_pause</item> <item>@string/moondrop_touch_action_play_pause</item>
<item>@string/pref_media_previous</item> <item>@string/pref_media_previous</item>
<item>@string/pref_media_next</item> <item>@string/pref_media_next</item>
<item>@string/pref_media_volumeup</item>
<item>@string/pref_media_volumedown</item>
<item>@string/pref_title_touch_voice_assistant</item> <item>@string/pref_title_touch_voice_assistant</item>
</string-array>
<string-array name="oppo_touch_tap_2_values">
<item>off</item>
<item>play_pause</item>
<item>previous</item>
<item>next</item>
<item>voice_assistant</item>
</string-array>
<string-array name="oppo_touch_tap_3_names">
<item>@string/sony_button_mode_off</item>
<item>@string/pref_title_touch_voice_assistant</item> <item>@string/pref_title_touch_voice_assistant</item>
<item>@string/prefs_game_mode</item> <item>@string/prefs_game_mode</item>
</string-array> </string-array>
<string-array name="oppo_touch_tap_3_values"> <string-array name="oppo_touch_tap_values">
<item>off</item> <!-- superset of values, unsupported are hidden by the coordinator -->
<item>voice_assistant</item>
<item>game_mode</item>
</string-array>
<string-array name="oppo_touch_hold_names">
<item>@string/sony_button_mode_off</item>
<item>@string/pref_media_volumeup</item>
<item>@string/pref_media_volumedown</item>
</string-array>
<string-array name="oppo_touch_hold_values">
<item>off</item> <item>off</item>
<item>play_pause</item>
<item>previous</item>
<item>next</item>
<item>volume_up</item> <item>volume_up</item>
<item>volume_down</item> <item>volume_down</item>
<item>voice_assistant</item>
<item>voice_assistant_realme</item>
<item>game_mode</item>
</string-array> </string-array>
<string-array name="soundcore_button_function_names"> <string-array name="soundcore_button_function_names">

View File

@ -2489,6 +2489,7 @@
<string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string> <string name="devicetype_nothing_cmf_watch_pro">CMF Watch Pro</string>
<string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string> <string name="devicetype_nothing_cmf_watch_pro_2">CMF Watch Pro 2</string>
<string name="devicetype_oppo_enco_air">Oppo Enco Air</string> <string name="devicetype_oppo_enco_air">Oppo Enco Air</string>
<string name="devicetype_realme_buds_t110">Realme Buds T110</string>
<string name="devicetype_galaxybuds">Galaxy Buds</string> <string name="devicetype_galaxybuds">Galaxy Buds</string>
<string name="devicetype_galaxybuds_live">Galaxy Buds Live</string> <string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
<string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string> <string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>

View File

@ -3,28 +3,29 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory <PreferenceCategory
android:key="oppo_touch_header_left"
android:title="@string/left_earbud" android:title="@string/left_earbud"
app:iconSpaceReserved="false"> app:iconSpaceReserved="false">
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_tap_2_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_tap_2_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_filter_2" android:icon="@drawable/ic_filter_2"
android:key="oppo_touch__left__tap_2" android:key="oppo_touch__left__tap_2"
android:summary="%s" android:summary="%s"
android:title="@string/double_tap" /> android:title="@string/double_tap" />
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_tap_3_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_tap_3_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_filter_3" android:icon="@drawable/ic_filter_3"
android:key="oppo_touch__left__tap_3" android:key="oppo_touch__left__tap_3"
android:summary="%s" android:summary="%s"
android:title="@string/triple_tap" /> android:title="@string/triple_tap" />
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_hold_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_hold_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_horizontal_rule" android:icon="@drawable/ic_horizontal_rule"
android:key="oppo_touch__left__hold" android:key="oppo_touch__left__hold"
android:summary="%s" android:summary="%s"
@ -32,31 +33,46 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="oppo_touch_header_right"
android:title="@string/right_earbud" android:title="@string/right_earbud"
app:iconSpaceReserved="false"> app:iconSpaceReserved="false">
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_tap_2_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_tap_2_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_filter_2" android:icon="@drawable/ic_filter_2"
android:key="oppo_touch__right__tap_2" android:key="oppo_touch__right__tap_2"
android:summary="%s" android:summary="%s"
android:title="@string/double_tap" /> android:title="@string/double_tap" />
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_tap_3_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_tap_3_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_filter_3" android:icon="@drawable/ic_filter_3"
android:key="oppo_touch__right__tap_3" android:key="oppo_touch__right__tap_3"
android:summary="%s" android:summary="%s"
android:title="@string/triple_tap" /> android:title="@string/triple_tap" />
<ListPreference <ListPreference
android:defaultValue="none" android:defaultValue="off"
android:entries="@array/oppo_touch_hold_names" android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_hold_values" android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_horizontal_rule" android:icon="@drawable/ic_horizontal_rule"
android:key="oppo_touch__right__hold" android:key="oppo_touch__right__hold"
android:summary="%s" android:summary="%s"
android:title="@string/long_press" /> android:title="@string/long_press" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:key="oppo_touch_header_both"
android:title="@string/moondrop_touch_earbud_both"
app:iconSpaceReserved="false">
<ListPreference
android:defaultValue="off"
android:entries="@array/oppo_touch_tap_names"
android:entryValues="@array/oppo_touch_tap_values"
android:icon="@drawable/ic_horizontal_rule"
android:key="oppo_touch__both__hold"
android:summary="%s"
android:title="@string/long_press" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>

View File

@ -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);
}
}