mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Merge pull request 'add support ldac, multipoint and game mode options for realme Buds T200' (#6305)
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/6305
This commit is contained in:
+44
-1
@@ -76,12 +76,39 @@ public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceC
|
|||||||
settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
|
settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
|
||||||
settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones);
|
settings.addSubScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS, R.xml.devicesettings_headphones);
|
||||||
|
|
||||||
|
if (this.supportsLdac(device) || this.supportsAnc(device)) {
|
||||||
|
settings.addRootScreen(DeviceSpecificSettingsScreen.AUDIO);
|
||||||
|
if (this.supportsLdac(device)) {
|
||||||
|
settings.addSubScreen(DeviceSpecificSettingsScreen.AUDIO, R.xml.devicesettings_ldac_toggle);
|
||||||
|
}
|
||||||
|
if (this.supportsAnc(device)) {
|
||||||
|
settings.addSubScreen(DeviceSpecificSettingsScreen.AUDIO, R.xml.devicesettings_onemore_noise_control_selector);
|
||||||
|
settings.addSubScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS, R.xml.devicesettings_oppo_headphones_touch_options_anc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.supportsMultipoint(device) || this.supportsGameMode(device)) {
|
||||||
|
settings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
|
||||||
|
if (this.supportsMultipoint(device)) {
|
||||||
|
settings.addSubScreen(DeviceSpecificSettingsScreen.CONNECTION, R.xml.devicesettings_oppo_headphones_multipoint);
|
||||||
|
}
|
||||||
|
if (this.supportsGameMode(device)) {
|
||||||
|
settings.addSubScreen(DeviceSpecificSettingsScreen.CONNECTION, R.xml.devicesettings_oppo_headphones_game_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
|
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
|
||||||
return new OppoHeadphonesSettingsCustomizer(getTouchOptions());
|
return new OppoHeadphonesSettingsCustomizer(
|
||||||
|
getTouchOptions(),
|
||||||
|
supportsLdac(device),
|
||||||
|
supportsMultipoint(device),
|
||||||
|
supportsGameMode(device),
|
||||||
|
supportsAnc(device)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions();
|
protected abstract Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions();
|
||||||
@@ -90,4 +117,20 @@ public abstract class OppoHeadphonesCoordinator extends AbstractBLClassicDeviceC
|
|||||||
public final DeviceKind getDeviceKind(@NonNull GBDevice device) {
|
public final DeviceKind getDeviceKind(@NonNull GBDevice device) {
|
||||||
return DeviceKind.EARBUDS;
|
return DeviceKind.EARBUDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsLdac(@NonNull GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsMultipoint(@NonNull GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsGameMode(@NonNull GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsAnc(@NonNull GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -22,10 +22,19 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchC
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.TouchConfigType;
|
||||||
|
|
||||||
public class OppoHeadphonesPreferences {
|
public class OppoHeadphonesPreferences {
|
||||||
public static String getKey(final TouchConfigSide side, final TouchConfigType type) {
|
public static final String TOUCH_PREFIX = "oppo_touch__";
|
||||||
|
|
||||||
|
public static final String LDAC = "pref_soundcore_ldac_mode";
|
||||||
|
public static final String MULTIPOINT = "oppo_multipoint";
|
||||||
|
public static final String GAME_MODE = "oppo_game_mode";
|
||||||
|
public static final String ANC_SELECTOR = "noise_control_selector";
|
||||||
|
public static final String ANC_TOUCH_CYCLE_MODES = "oppo_touch_anc_cycle_modes";
|
||||||
|
|
||||||
|
public static String getTouchKey(final TouchConfigSide side, final TouchConfigType type) {
|
||||||
return String.format(
|
return String.format(
|
||||||
Locale.ROOT,
|
Locale.ROOT,
|
||||||
"oppo_touch__%s__%s",
|
"%s%s__%s",
|
||||||
|
TOUCH_PREFIX,
|
||||||
side.name().toLowerCase(Locale.ROOT),
|
side.name().toLowerCase(Locale.ROOT),
|
||||||
type.name().toLowerCase(Locale.ROOT)
|
type.name().toLowerCase(Locale.ROOT)
|
||||||
);
|
);
|
||||||
|
|||||||
+59
-4
@@ -18,9 +18,13 @@ package nodomain.freeyourgadget.gadgetbridge.devices.oppo;
|
|||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.MultiSelectListPreference;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -40,10 +44,19 @@ 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;
|
private final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions;
|
||||||
|
private final boolean supportsLdac;
|
||||||
|
private final boolean supportsMultipoint;
|
||||||
|
private final boolean supportsGameMode;
|
||||||
|
private final boolean supportsAnc;
|
||||||
|
|
||||||
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) {
|
||||||
|
final boolean supportsLdac = in.readByte() == 1;
|
||||||
|
final boolean supportsMultipoint = in.readByte() == 1;
|
||||||
|
final boolean supportsGameMode = in.readByte() == 1;
|
||||||
|
final boolean supportsAnc = in.readByte() == 1;
|
||||||
|
|
||||||
final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions = new LinkedHashMap<>();
|
final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions = new LinkedHashMap<>();
|
||||||
final int numOptions = in.readInt();
|
final int numOptions = in.readInt();
|
||||||
for (int i = 0; i < numOptions; i++) {
|
for (int i = 0; i < numOptions; i++) {
|
||||||
@@ -53,7 +66,8 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
in.readList(values, TouchConfigValue.class.getClassLoader());
|
in.readList(values, TouchConfigValue.class.getClassLoader());
|
||||||
touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values);
|
touchOptions.put(Pair.create(touchConfigSide, touchConfigType), values);
|
||||||
}
|
}
|
||||||
return new OppoHeadphonesSettingsCustomizer(touchOptions);
|
|
||||||
|
return new OppoHeadphonesSettingsCustomizer(touchOptions, supportsLdac, supportsMultipoint, supportsGameMode, supportsAnc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,12 +76,32 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public OppoHeadphonesSettingsCustomizer(final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions) {
|
public OppoHeadphonesSettingsCustomizer(final Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> touchOptions, final boolean supportsLdac, final boolean supportsMultipoint, final boolean supportsGameMode, final boolean supportsAnc) {
|
||||||
this.touchOptions = touchOptions;
|
this.touchOptions = touchOptions;
|
||||||
|
this.supportsLdac = supportsLdac;
|
||||||
|
this.supportsMultipoint = supportsMultipoint;
|
||||||
|
this.supportsGameMode = supportsGameMode;
|
||||||
|
this.supportsAnc = supportsAnc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
|
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
|
||||||
|
if (OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES.equals(preference.getKey())) {
|
||||||
|
if (preference instanceof MultiSelectListPreference) {
|
||||||
|
final MultiSelectListPreference ancCycleModesPref = (MultiSelectListPreference) preference;
|
||||||
|
final Set<String> selectedValues = ancCycleModesPref.getValues();
|
||||||
|
|
||||||
|
if (selectedValues == null || selectedValues.size() < 2) {
|
||||||
|
final Context context = preference.getContext();
|
||||||
|
final String message = context.getString(nodomain.freeyourgadget.gadgetbridge.R.string.select_at_least_option, 2);
|
||||||
|
new MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(nodomain.freeyourgadget.gadgetbridge.R.string.warning)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,6 +109,12 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
final Set<TouchConfigSide> knownSides = new HashSet<>();
|
final Set<TouchConfigSide> knownSides = new HashSet<>();
|
||||||
final Set<TouchConfigType> knownTypes = new HashSet<>();
|
final Set<TouchConfigType> knownTypes = new HashSet<>();
|
||||||
|
|
||||||
|
this.addPreferenceHandler(handler, OppoHeadphonesPreferences.LDAC, supportsLdac);
|
||||||
|
this.addPreferenceHandler(handler, OppoHeadphonesPreferences.MULTIPOINT, supportsMultipoint);
|
||||||
|
this.addPreferenceHandler(handler, OppoHeadphonesPreferences.GAME_MODE, supportsGameMode);
|
||||||
|
this.addPreferenceHandler(handler, OppoHeadphonesPreferences.ANC_SELECTOR, supportsAnc);
|
||||||
|
this.addPreferenceHandler(handler, OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, supportsAnc);
|
||||||
|
|
||||||
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
|
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
|
||||||
final TouchConfigSide side = e.getKey().first;
|
final TouchConfigSide side = e.getKey().first;
|
||||||
final TouchConfigType type = e.getKey().second;
|
final TouchConfigType type = e.getKey().second;
|
||||||
@@ -83,7 +123,7 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
knownSides.add(side);
|
knownSides.add(side);
|
||||||
knownTypes.add(type);
|
knownTypes.add(type);
|
||||||
|
|
||||||
final String key = OppoHeadphonesPreferences.getKey(side, type);
|
final String key = OppoHeadphonesPreferences.getTouchKey(side, type);
|
||||||
final ListPreference pref = handler.findPreference(key);
|
final ListPreference pref = handler.findPreference(key);
|
||||||
if (pref == null) {
|
if (pref == null) {
|
||||||
continue;
|
continue;
|
||||||
@@ -120,7 +160,7 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
|
|
||||||
for (final TouchConfigType type : TouchConfigType.values()) {
|
for (final TouchConfigType type : TouchConfigType.values()) {
|
||||||
if (!knownTypes.contains(type)) {
|
if (!knownTypes.contains(type)) {
|
||||||
final String key = OppoHeadphonesPreferences.getKey(side, type);
|
final String key = OppoHeadphonesPreferences.getTouchKey(side, type);
|
||||||
final Preference pref = handler.findPreference(key);
|
final Preference pref = handler.findPreference(key);
|
||||||
if (pref != null) {
|
if (pref != null) {
|
||||||
pref.setVisible(false);
|
pref.setVisible(false);
|
||||||
@@ -130,6 +170,16 @@ public class OppoHeadphonesSettingsCustomizer implements DeviceSpecificSettingsC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addPreferenceHandler(DeviceSpecificSettingsHandler handler, String key, boolean isSupported) {
|
||||||
|
Preference pref = handler.findPreference(key);
|
||||||
|
if (pref != null) {
|
||||||
|
pref.setVisible(isSupported);
|
||||||
|
if (isSupported) {
|
||||||
|
handler.addPreferenceHandlerFor(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getPreferenceKeysWithSummary() {
|
public Set<String> getPreferenceKeysWithSummary() {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
@@ -142,6 +192,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.writeByte((byte) (supportsLdac ? 1 : 0));
|
||||||
|
dest.writeByte((byte) (supportsMultipoint ? 1 : 0));
|
||||||
|
dest.writeByte((byte) (supportsGameMode ? 1 : 0));
|
||||||
|
dest.writeByte((byte) (supportsAnc ? 1 : 0));
|
||||||
|
|
||||||
dest.writeInt(touchOptions.size());
|
dest.writeInt(touchOptions.size());
|
||||||
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
|
for (final Map.Entry<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> e : touchOptions.entrySet()) {
|
||||||
dest.writeString(e.getKey().first.name());
|
dest.writeString(e.getKey().first.name());
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
/* 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 androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
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.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||||
|
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 RealmeBudsAir6ProCoordinator extends OppoHeadphonesCoordinator {
|
||||||
|
@Override
|
||||||
|
protected Pattern getSupportedDeviceName() {
|
||||||
|
return Pattern.compile("realme Buds Air6 Pro", Pattern.LITERAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturer() {
|
||||||
|
return "Realme";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDeviceNameResource() {
|
||||||
|
return R.string.devicetype_realme_buds_air_6_pro;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatteryConfig[] getBatteryConfig(final GBDevice device) {
|
||||||
|
final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_realme_buds_t300_l, R.string.left_earbud);
|
||||||
|
final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_realme_buds_t300_r, R.string.right_earbud);
|
||||||
|
final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_realme_buds_t300_case, R.string.battery_case);
|
||||||
|
return new BatteryConfig[]{battery1, battery2, battery3};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefaultIconResource() {
|
||||||
|
return R.drawable.ic_realme_buds_t300;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFindDevice(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsLdac(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsMultipoint(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsGameMode(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAnc(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
|
||||||
|
return new LinkedHashMap<>() {{
|
||||||
|
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,
|
||||||
|
TouchConfigValue.GAME_MODE,
|
||||||
|
TouchConfigValue.NOISE_CONTROL
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -67,6 +67,26 @@ public class RealmeBudsT200Coordinator extends OppoHeadphonesCoordinator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsLdac(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsMultipoint(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsGameMode(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAnc(@NonNull GBDevice device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
|
protected Map<Pair<TouchConfigSide, TouchConfigType>, List<TouchConfigValue>> getTouchOptions() {
|
||||||
return new LinkedHashMap<>() {{
|
return new LinkedHashMap<>() {{
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.polar.PolarH9DeviceCoordinat
|
|||||||
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.RealmeBudsAir5ProCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsAir5ProCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsAir6ProCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT100Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT100Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT110Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT110Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT200Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.realme.RealmeBudsT200Coordinator;
|
||||||
@@ -922,6 +923,7 @@ public enum DeviceType {
|
|||||||
REALME_BUDS_T200(RealmeBudsT200Coordinator.class),
|
REALME_BUDS_T200(RealmeBudsT200Coordinator.class),
|
||||||
REALME_BUDS_T300(RealmeBudsT300Coordinator.class),
|
REALME_BUDS_T300(RealmeBudsT300Coordinator.class),
|
||||||
REALME_BUDS_AIR_5_PRO(RealmeBudsAir5ProCoordinator.class),
|
REALME_BUDS_AIR_5_PRO(RealmeBudsAir5ProCoordinator.class),
|
||||||
|
REALME_BUDS_AIR_6_PRO(RealmeBudsAir6ProCoordinator.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),
|
||||||
|
|||||||
+452
-122
@@ -26,7 +26,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
@@ -41,6 +47,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.OppoCo
|
|||||||
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.service.devices.oppo.commands.TouchConfigValue;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.MiscConfigType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigValue;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.SubscriptionType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
|
||||||
|
|
||||||
@@ -137,19 +147,11 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
events.addAll(parseBattery(payload));
|
events.addAll(parseBattery(payload));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DEVICE_INFO: {
|
case SUBSCRIPTION_ACK:
|
||||||
switch (payload[0]) {
|
LOG.debug("Got subscription ack, return={}", StringUtils.bytesToHex(payload));
|
||||||
case 1: // battery
|
break;
|
||||||
events.addAll(parseBattery(payload));
|
case SUBSCRIPTION_RET: {
|
||||||
break;
|
events.addAll(parseSubscription(payload));
|
||||||
case 2: // status
|
|
||||||
LOG.debug("Got status");
|
|
||||||
// TODO handle
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG.warn("Unknown device info {}", payload[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FIRMWARE_RET: {
|
case FIRMWARE_RET: {
|
||||||
@@ -157,126 +159,67 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
LOG.warn("Unexpected firmware ret {}", payload[0]);
|
LOG.warn("Unexpected firmware ret {}", payload[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
events.addAll(parseFirmware(payload));
|
||||||
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)).strip();
|
|
||||||
}
|
|
||||||
final String[] parts = fwString.split(",");
|
|
||||||
if (parts.length % 3 != 0) {
|
|
||||||
LOG.warn("Fw parts length {} from '{}' is not divisible by 3", parts.length, fwString);
|
|
||||||
|
|
||||||
// We need to persist something, otherwise Gb misbehaves
|
|
||||||
final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
|
|
||||||
eventVersionInfo.fwVersion = fwString;
|
|
||||||
eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
|
|
||||||
events.add(eventVersionInfo);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
final String[] fwVersionParts = new String[3];
|
|
||||||
for (int i = 0; i < parts.length; i += 3) {
|
|
||||||
final String versionPart = parts[i];
|
|
||||||
final String versionType = parts[i + 1];
|
|
||||||
final String version = parts[i + 2];
|
|
||||||
if (!"2".equals(versionType)) {
|
|
||||||
continue; // not fw
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (versionPart) {
|
|
||||||
case "1":
|
|
||||||
fwVersionParts[0] = version;
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
fwVersionParts[1] = version;
|
|
||||||
break;
|
|
||||||
case "3":
|
|
||||||
fwVersionParts[2] = version;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG.warn("Unknown firmware version part {}", versionPart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
eventVersionInfo.fwVersion = fwVersion;
|
|
||||||
eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
|
|
||||||
events.add(eventVersionInfo);
|
|
||||||
|
|
||||||
LOG.debug("Got fw version: {}", fwVersion);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FIND_DEVICE_ACK: {
|
case FIND_DEVICE_ACK: {
|
||||||
LOG.debug("Got find device ack, status={}", payload[0]);
|
LOG.debug("Got find device ack, status={}", payload[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MISC_CONFIG_RET: {
|
||||||
|
if (payload[0] != 0) {
|
||||||
|
LOG.warn("Unknown misc config ret {}", payload[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (payload.length < 3) {
|
||||||
|
LOG.warn("Unexpected misc config ret payload size {}", payload.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.add(parseMiscConfig(payload));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MISC_CONFIG_ACK: {
|
||||||
|
LOG.debug("Got misc config ack, status={}", payload[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TOUCH_CONFIG_RET: {
|
case TOUCH_CONFIG_RET: {
|
||||||
if (payload[0] != 0) {
|
if (payload[0] != 0) {
|
||||||
LOG.warn("Unknown config ret {}", payload[0]);
|
LOG.warn("Unknown touch config ret {}", payload[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((payload.length - 2) % 4 != 0) {
|
if ((payload.length - 2) % 4 != 0) {
|
||||||
LOG.warn("Unexpected config ret payload size {}", payload.length);
|
LOG.warn("Unexpected touch config ret payload size {}", payload.length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
|
events.add(parseTouchConfig(payload));
|
||||||
|
|
||||||
for (int i = 2; i < payload.length; i += 4) {
|
|
||||||
final int sideCode = payload[i] & 0xff;
|
|
||||||
final int typeCode = BLETypeConversions.toUint16(payload, i + 1);
|
|
||||||
final int valueCode = payload[i + 3] & 0xff;
|
|
||||||
final TouchConfigSide side = TouchConfigSide.fromCode(sideCode);
|
|
||||||
final TouchConfigType type = TouchConfigType.fromCode(typeCode);
|
|
||||||
final TouchConfigValue value = TouchConfigValue.fromCode(valueCode);
|
|
||||||
|
|
||||||
if (side == null) {
|
|
||||||
LOG.warn("Unknown touch side code {}", sideCode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (type == null) {
|
|
||||||
LOG.warn("Unknown touch type code {}", typeCode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (value == null) {
|
|
||||||
LOG.warn("Unknown touch value code {}", valueCode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug("Got touch config for {} {} = {}", side, type, value);
|
|
||||||
|
|
||||||
eventUpdatePreferences.withPreference(
|
|
||||||
OppoHeadphonesPreferences.getKey(side, type),
|
|
||||||
value.name().toLowerCase(Locale.ROOT)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
events.add(eventUpdatePreferences);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TOUCH_CONFIG_ACK: {
|
case TOUCH_CONFIG_ACK: {
|
||||||
LOG.debug("Got config ack, status={}", payload[0]);
|
LOG.debug("Got touch config ack, status={}", payload[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
case ANC_CONFIG_RET: {
|
||||||
|
if (payload[0] != 0) {
|
||||||
|
LOG.warn("Unknown anc config ret {}", payload[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (payload.length < 3) {
|
||||||
|
LOG.warn("Unexpected anc config ret payload size {}", payload.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.add(parseAncConfig(payload));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ANC_CONFIG_ACK: {
|
||||||
|
LOG.debug("Got anc config ack, status={}", payload[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
LOG.warn("Unhandled command {}", command);
|
LOG.warn("Unhandled command {}", command);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
@@ -297,6 +240,10 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int batteryLevel = payload[i + 1] & 0x7f;
|
final int batteryLevel = payload[i + 1] & 0x7f;
|
||||||
|
if (batteryIndex == 2 && batteryLevel == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final BatteryState batteryState = (payload[i + 1] & 0x80) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
|
final BatteryState batteryState = (payload[i + 1] & 0x80) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
|
||||||
|
|
||||||
LOG.debug("Got battery {}: {}%, {}", batteryIndex, batteryLevel, batteryState);
|
LOG.debug("Got battery {}: {}%, {}", batteryIndex, batteryLevel, batteryState);
|
||||||
@@ -308,9 +255,264 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
events.add(eventBatteryInfo);
|
events.add(eventBatteryInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Integer> processedBatteries = events.stream()
|
||||||
|
.map(event -> ((GBDeviceEventBatteryInfo) event).batteryIndex)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (processedBatteries.contains(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final GBDeviceEventBatteryInfo eventBatteryInfo = new GBDeviceEventBatteryInfo();
|
||||||
|
eventBatteryInfo.batteryIndex = i;
|
||||||
|
eventBatteryInfo.level = -1;
|
||||||
|
eventBatteryInfo.state = BatteryState.UNKNOWN;
|
||||||
|
events.add(eventBatteryInfo);
|
||||||
|
}
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<GBDeviceEvent> parseFirmware(final byte[] payload) {
|
||||||
|
final List<GBDeviceEvent> events = new ArrayList<>();
|
||||||
|
|
||||||
|
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)).strip();
|
||||||
|
}
|
||||||
|
final String[] parts = fwString.split(",");
|
||||||
|
if (parts.length % 3 != 0) {
|
||||||
|
LOG.warn("Fw parts length {} from '{}' is not divisible by 3", parts.length, fwString);
|
||||||
|
|
||||||
|
// We need to persist something, otherwise Gb misbehaves
|
||||||
|
final GBDeviceEventVersionInfo eventVersionInfo = new GBDeviceEventVersionInfo();
|
||||||
|
eventVersionInfo.fwVersion = fwString;
|
||||||
|
eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
|
||||||
|
events.add(eventVersionInfo);
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
final String[] fwVersionParts = new String[3];
|
||||||
|
for (int i = 0; i < parts.length; i += 3) {
|
||||||
|
final String versionPart = parts[i];
|
||||||
|
final String versionType = parts[i + 1];
|
||||||
|
final String version = parts[i + 2];
|
||||||
|
if (!"2".equals(versionType)) {
|
||||||
|
continue; // not fw
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (versionPart) {
|
||||||
|
case "1":
|
||||||
|
fwVersionParts[0] = version;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
fwVersionParts[1] = version;
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
fwVersionParts[2] = version;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("Unknown firmware version part {}", versionPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
eventVersionInfo.fwVersion = fwVersion;
|
||||||
|
eventVersionInfo.hwVersion = GBApplication.getContext().getString(R.string.n_a);
|
||||||
|
events.add(eventVersionInfo);
|
||||||
|
|
||||||
|
LOG.debug("Got fw version: {}", fwVersion);
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<GBDeviceEvent> parseSubscription(final byte[] payload) {
|
||||||
|
final List<GBDeviceEvent> events = new ArrayList<>();
|
||||||
|
final int typeCode = payload[0] & 0xff;
|
||||||
|
final SubscriptionType type = SubscriptionType.fromCode(typeCode);
|
||||||
|
if (type == null) {
|
||||||
|
LOG.warn("Unknown subcription type {}", String.format(Locale.ROOT, "0x%02x", typeCode));
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
|
||||||
|
switch (type) {
|
||||||
|
case BATTERY: {
|
||||||
|
events.addAll(parseBattery(payload));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATUS: {
|
||||||
|
LOG.debug("Got status");
|
||||||
|
// TODO handle
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ANC_SELECTOR: {
|
||||||
|
final int valueCode = payload[2] & 0xFF;
|
||||||
|
final AncConfigValue mode = AncConfigValue.fromCode(valueCode);
|
||||||
|
if (mode == null) {
|
||||||
|
LOG.warn("Unknown anc mode code {}", valueCode);
|
||||||
|
}
|
||||||
|
LOG.debug("Got anc config for MODE = {}", mode);
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.ANC_SELECTOR,
|
||||||
|
mode.getPrefId()
|
||||||
|
);
|
||||||
|
events.add(eventUpdatePreferences);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GAME_MODE: {
|
||||||
|
final boolean isEnabled = ((payload[1] & 0xFF) == 0x01);
|
||||||
|
LOG.debug("Got misc config for GAME_MODE = {}", isEnabled);
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.GAME_MODE,
|
||||||
|
isEnabled
|
||||||
|
);
|
||||||
|
events.add(eventUpdatePreferences);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
LOG.warn("Unhandled subscription type {}", type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GBDeviceEvent parseMiscConfig(final byte[] payload) {
|
||||||
|
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
|
||||||
|
for (int i = 2; i + 1 < payload.length; i += 2) {
|
||||||
|
final int typeCode = payload[i] & 0xff;
|
||||||
|
final int valueCode = payload[i + 1] & 0xff;
|
||||||
|
|
||||||
|
final MiscConfigType type = MiscConfigType.fromCode(typeCode);
|
||||||
|
if (type == null) {
|
||||||
|
LOG.warn("Unknown misc config type code {}", typeCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isEnabled = (valueCode == 1);
|
||||||
|
switch (type) {
|
||||||
|
case LDAC:
|
||||||
|
LOG.debug("Got misc config for LDAC = {}", isEnabled);
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.LDAC,
|
||||||
|
isEnabled
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case MULTIPOINT:
|
||||||
|
LOG.debug("Got misc config for MULTIPOINT = {}", isEnabled);
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.MULTIPOINT,
|
||||||
|
isEnabled
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case GAME_MODE:
|
||||||
|
LOG.debug("Got misc config for GAME_MODE = {}", isEnabled);
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.GAME_MODE,
|
||||||
|
isEnabled
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("Unknown misc config type code {}", typeCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventUpdatePreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GBDeviceEvent parseTouchConfig(final byte[] payload) {
|
||||||
|
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences();
|
||||||
|
|
||||||
|
for (int i = 2; i < payload.length; i += 4) {
|
||||||
|
final int sideCode = payload[i] & 0xff;
|
||||||
|
final int typeCode = BLETypeConversions.toUint16(payload, i + 1);
|
||||||
|
final int valueCode = payload[i + 3] & 0xff;
|
||||||
|
final TouchConfigSide side = TouchConfigSide.fromCode(sideCode);
|
||||||
|
final TouchConfigType type = TouchConfigType.fromCode(typeCode);
|
||||||
|
final TouchConfigValue value = TouchConfigValue.fromCode(valueCode);
|
||||||
|
|
||||||
|
if (side == null) {
|
||||||
|
LOG.warn("Unknown touch side code {}", sideCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (type == null) {
|
||||||
|
LOG.warn("Unknown touch type code {}", typeCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (value == null) {
|
||||||
|
LOG.warn("Unknown touch value code {}", valueCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Got touch config for {} {} = {}", side, type, value);
|
||||||
|
|
||||||
|
eventUpdatePreferences.withPreference(
|
||||||
|
OppoHeadphonesPreferences.getTouchKey(side, type),
|
||||||
|
value.name().toLowerCase(Locale.ROOT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return eventUpdatePreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GBDeviceEvent parseAncConfig(final byte[] payload) {
|
||||||
|
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences();
|
||||||
|
|
||||||
|
final int typeCode = payload[1] & 0xff;
|
||||||
|
final int valueCode = payload[3] & 0xff;
|
||||||
|
|
||||||
|
final AncConfigType type = AncConfigType.fromCode(typeCode);
|
||||||
|
if (type == null) {
|
||||||
|
LOG.warn("Unknown anc type code {}", typeCode);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case MODE: {
|
||||||
|
final AncConfigValue mode = AncConfigValue.fromCode(valueCode);
|
||||||
|
if (mode == null) {
|
||||||
|
LOG.warn("Unknown anc mode code {}", valueCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Got anc config for {} = {}", type, mode);
|
||||||
|
event.withPreference(OppoHeadphonesPreferences.ANC_SELECTOR, mode.getPrefId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TOUCH_CYCLE_MODES: {
|
||||||
|
final EnumSet<AncConfigValue> modes = AncConfigValue.fromMask(valueCode);
|
||||||
|
if (!modes.isEmpty()) {
|
||||||
|
final Set<String> prefIds = AncConfigValue.toPrefIds(modes);
|
||||||
|
|
||||||
|
LOG.debug("Got anc config for {} = {}", type, prefIds);
|
||||||
|
event.withPreference(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, prefIds);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
LOG.debug("Unknown anc type code {}", typeCode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] encodeFirmwareVersionReq() {
|
public byte[] encodeFirmwareVersionReq() {
|
||||||
return encodeMessage(OppoCommand.FIRMWARE_GET, new byte[0]);
|
return encodeMessage(OppoCommand.FIRMWARE_GET, new byte[0]);
|
||||||
@@ -325,11 +527,11 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
public byte[] encodeSendConfiguration(final String config) {
|
public byte[] encodeSendConfiguration(final String config) {
|
||||||
final DevicePrefs prefs = getDevicePrefs();
|
final DevicePrefs prefs = getDevicePrefs();
|
||||||
|
|
||||||
if (config.startsWith("oppo_touch__")) {
|
if (config.startsWith(OppoHeadphonesPreferences.TOUCH_PREFIX)) {
|
||||||
final String[] parts = config.split("__");
|
final String[] parts = config.split("__");
|
||||||
final TouchConfigSide side = TouchConfigSide.valueOf(parts[1].toUpperCase(Locale.ROOT));
|
final TouchConfigSide side = TouchConfigSide.valueOf(parts[1].toUpperCase(Locale.ROOT));
|
||||||
final TouchConfigType type = TouchConfigType.valueOf(parts[2].toUpperCase(Locale.ROOT));
|
final TouchConfigType type = TouchConfigType.valueOf(parts[2].toUpperCase(Locale.ROOT));
|
||||||
final String valueCode = prefs.getString(OppoHeadphonesPreferences.getKey(side, type), null);
|
final String valueCode = prefs.getString(OppoHeadphonesPreferences.getTouchKey(side, type), null);
|
||||||
if (valueCode == null) {
|
if (valueCode == null) {
|
||||||
LOG.warn("Failed to get touch option value for {}/{}", side, type);
|
LOG.warn("Failed to get touch option value for {}/{}", side, type);
|
||||||
return super.encodeSendConfiguration(config);
|
return super.encodeSendConfiguration(config);
|
||||||
@@ -338,14 +540,48 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
final TouchConfigValue value = TouchConfigValue.valueOf(valueCode.toUpperCase(Locale.ROOT));
|
final TouchConfigValue value = TouchConfigValue.valueOf(valueCode.toUpperCase(Locale.ROOT));
|
||||||
|
|
||||||
LOG.debug("Sending {} {} = {}", side, type, value);
|
LOG.debug("Sending {} {} = {}", side, type, value);
|
||||||
|
return encodeTouchConfigSet(side, type, value);
|
||||||
|
}
|
||||||
|
|
||||||
final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN);
|
if (config.equals(OppoHeadphonesPreferences.LDAC)) {
|
||||||
buf.put((byte) 0x01);
|
final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.LDAC, false);
|
||||||
buf.put((byte) side.getCode());
|
LOG.debug("Sending ldac = {}", value);
|
||||||
buf.putShort((short) type.getCode());
|
return encodeLdacSet(value);
|
||||||
buf.put((byte) value.getCode());
|
}
|
||||||
|
|
||||||
return encodeMessage(OppoCommand.TOUCH_CONFIG_SET, buf.array());
|
if (config.equals(OppoHeadphonesPreferences.MULTIPOINT)) {
|
||||||
|
final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.MULTIPOINT, false);
|
||||||
|
LOG.debug("Sending multipoint = {}", value);
|
||||||
|
return encodeMultipointSet(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.equals(OppoHeadphonesPreferences.GAME_MODE)) {
|
||||||
|
final boolean value = prefs.getBoolean(OppoHeadphonesPreferences.GAME_MODE, false);
|
||||||
|
LOG.debug("Sending game mode = {}", value);
|
||||||
|
return encodeGameModeSet(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.equals(OppoHeadphonesPreferences.ANC_SELECTOR)) {
|
||||||
|
final String prefId = prefs.getString(OppoHeadphonesPreferences.ANC_SELECTOR, "0");
|
||||||
|
AncConfigValue mode = AncConfigValue.fromPrefId(prefId);
|
||||||
|
if (mode == null) {
|
||||||
|
LOG.debug("Unknown ANC prefId = \"{}\"", prefId);
|
||||||
|
mode = AncConfigValue.OFF;
|
||||||
|
}
|
||||||
|
LOG.debug("Sending ANC mode = {}", mode);
|
||||||
|
return encodeAncModeSet(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.equals(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES)) {
|
||||||
|
final Set<String> prefIds = prefs.getStringSet(OppoHeadphonesPreferences.ANC_TOUCH_CYCLE_MODES, Collections.emptySet());
|
||||||
|
final EnumSet<AncConfigValue> modes = AncConfigValue.fromPrefIds(prefIds);
|
||||||
|
if (modes.size() < 2) {
|
||||||
|
LOG.warn("ANC cycle must contain at least 2 modes. Current selection: {}", modes);
|
||||||
|
return super.encodeSendConfiguration(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Sending ANC touch cycle modes = {}", modes);
|
||||||
|
return encodeAncTouchCycleModesSet(modes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.encodeSendConfiguration(config);
|
return super.encodeSendConfiguration(config);
|
||||||
@@ -355,10 +591,104 @@ public class OppoHeadphonesProtocol extends GBDeviceProtocol {
|
|||||||
return encodeMessage(OppoCommand.BATTERY_REQ, new byte[0]);
|
return encodeMessage(OppoCommand.BATTERY_REQ, new byte[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encodeConfigurationReq() {
|
private byte[] encodeTouchConfigSet(final TouchConfigSide side, final TouchConfigType type, final TouchConfigValue value) {
|
||||||
|
final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 0x01);
|
||||||
|
buf.put((byte) side.getCode());
|
||||||
|
buf.putShort((short) type.getCode());
|
||||||
|
buf.put((byte) value.getCode());
|
||||||
|
return encodeMessage(OppoCommand.TOUCH_CONFIG_SET, buf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeTouchConfigReq() {
|
||||||
return encodeMessage(OppoCommand.TOUCH_CONFIG_REQ, new byte[]{0x02, 0x03, 0x01});
|
return encodeMessage(OppoCommand.TOUCH_CONFIG_REQ, new byte[]{0x02, 0x03, 0x01});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] encodeLdacSet(final boolean enable) {
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) (enable ? 0x01 : 0x00)
|
||||||
|
};
|
||||||
|
return encodeMiscConfigSet(MiscConfigType.LDAC, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeGameModeSet(final boolean enable) {
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) (enable ? 0x01 : 0x00)
|
||||||
|
};
|
||||||
|
return encodeMiscConfigSet(MiscConfigType.GAME_MODE, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeMultipointSet(final boolean enable) {
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) (enable ? 0x01 : 0x00)
|
||||||
|
};
|
||||||
|
return encodeMiscConfigSet(MiscConfigType.MULTIPOINT, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] encodeMiscConfigSet(final MiscConfigType type, final byte[] value) {
|
||||||
|
final byte[] payload = new byte[1 + value.length];
|
||||||
|
payload[0] = (byte) type.getCode();
|
||||||
|
System.arraycopy(value, 0, payload, 1, value.length);
|
||||||
|
return encodeMessage(OppoCommand.MISC_CONFIG_SET, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeMiscConfigReq(List<MiscConfigType> types) {
|
||||||
|
if (types == null || types.isEmpty()) {
|
||||||
|
return encodeMessage(OppoCommand.MISC_CONFIG_REQ, new byte[]{0x00});
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] payload = new byte[1 + types.size()];
|
||||||
|
payload[0] = (byte) types.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < types.size(); i++) {
|
||||||
|
payload[i + 1] = (byte) types.get(i).getCode();
|
||||||
|
}
|
||||||
|
return encodeMessage(OppoCommand.MISC_CONFIG_REQ, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeAncModeSet(final AncConfigValue mode) {
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) AncConfigType.MODE.getCode(),
|
||||||
|
(byte) 0x01,
|
||||||
|
(byte) mode.getCode()
|
||||||
|
};
|
||||||
|
return encodeMessage(OppoCommand.ANC_CONFIG_SET, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeAncTouchCycleModesSet(final EnumSet<AncConfigValue> modes) {
|
||||||
|
final int mask = AncConfigValue.toMask(modes);
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) AncConfigType.TOUCH_CYCLE_MODES.getCode(),
|
||||||
|
(byte) 0x01,
|
||||||
|
(byte) mask
|
||||||
|
};
|
||||||
|
return encodeMessage(OppoCommand.ANC_CONFIG_SET, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeAncConfigReq(final AncConfigType type) {
|
||||||
|
final byte[] payload = new byte[] {
|
||||||
|
(byte) type.getCode(),
|
||||||
|
(byte) 0x01,
|
||||||
|
};
|
||||||
|
return encodeMessage(OppoCommand.ANC_CONFIG_REQ, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeSubscriptionSet(@NonNull final EnumSet<SubscriptionType> subscriptions) {
|
||||||
|
if (subscriptions.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Subscription list cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] payload = new byte[1 + subscriptions.size()];
|
||||||
|
payload[0] = (byte) 0x09;
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (SubscriptionType type : subscriptions) {
|
||||||
|
payload[i++] = (byte) type.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeMessage(OppoCommand.SUBSCRIPTION_SET, payload);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] encodeMessage(final OppoCommand command, final byte[] payload) {
|
private byte[] encodeMessage(final OppoCommand command, final byte[] payload) {
|
||||||
final ByteBuffer buf = ByteBuffer.allocate(9 + payload.length).order(ByteOrder.LITTLE_ENDIAN);
|
final ByteBuffer buf = ByteBuffer.allocate(9 + payload.length).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
buf.put(CMD_PREAMBLE);
|
buf.put(CMD_PREAMBLE);
|
||||||
|
|||||||
+36
-1
@@ -22,11 +22,18 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractHeadphoneSerialDeviceSupportV2;
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractHeadphoneSerialDeviceSupportV2;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.MiscConfigType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.AncConfigType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.oppo.commands.SubscriptionType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoHeadphonesCoordinator;
|
||||||
|
|
||||||
public class OppoHeadphonesSupport extends AbstractHeadphoneSerialDeviceSupportV2<OppoHeadphonesProtocol> {
|
public class OppoHeadphonesSupport extends AbstractHeadphoneSerialDeviceSupportV2<OppoHeadphonesProtocol> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(OppoHeadphonesSupport.class);
|
private static final Logger LOG = LoggerFactory.getLogger(OppoHeadphonesSupport.class);
|
||||||
@@ -69,8 +76,36 @@ public class OppoHeadphonesSupport extends AbstractHeadphoneSerialDeviceSupportV
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
|
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
|
||||||
|
|
||||||
|
final OppoHeadphonesCoordinator coordinator = (OppoHeadphonesCoordinator) getDevice().getDeviceCoordinator();
|
||||||
|
final List<MiscConfigType> supportedMiscConfigs = new ArrayList<>();
|
||||||
|
final EnumSet<SubscriptionType> supportedSubscriptions = EnumSet.of(SubscriptionType.BATTERY);
|
||||||
|
if (coordinator.supportsLdac(getDevice())) {
|
||||||
|
supportedMiscConfigs.add(MiscConfigType.LDAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coordinator.supportsMultipoint(getDevice())) {
|
||||||
|
supportedMiscConfigs.add(MiscConfigType.MULTIPOINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coordinator.supportsGameMode(getDevice())) {
|
||||||
|
supportedMiscConfigs.add(MiscConfigType.GAME_MODE);
|
||||||
|
supportedSubscriptions.add(SubscriptionType.GAME_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coordinator.supportsAnc(getDevice())) {
|
||||||
|
builder.write(mDeviceProtocol.encodeAncConfigReq(AncConfigType.MODE));
|
||||||
|
builder.write(mDeviceProtocol.encodeAncConfigReq(AncConfigType.TOUCH_CYCLE_MODES));
|
||||||
|
supportedSubscriptions.add(SubscriptionType.ANC_SELECTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportedMiscConfigs.isEmpty()) {
|
||||||
|
builder.write(mDeviceProtocol.encodeMiscConfigReq(supportedMiscConfigs));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.write(mDeviceProtocol.encodeSubscriptionSet(supportedSubscriptions));
|
||||||
|
builder.write(mDeviceProtocol.encodeTouchConfigReq());
|
||||||
builder.write(mDeviceProtocol.encodeFirmwareVersionReq());
|
builder.write(mDeviceProtocol.encodeFirmwareVersionReq());
|
||||||
builder.write(mDeviceProtocol.encodeConfigurationReq());
|
|
||||||
builder.write(mDeviceProtocol.encodeBatteryReq());
|
builder.write(mDeviceProtocol.encodeBatteryReq());
|
||||||
builder.setDeviceState(GBDevice.State.INITIALIZED);
|
builder.setDeviceState(GBDevice.State.INITIALIZED);
|
||||||
scheduleBatteryRequestRetry();
|
scheduleBatteryRequestRetry();
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/* 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.service.devices.oppo.commands;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum AncConfigType {
|
||||||
|
MODE(0x01),
|
||||||
|
TOUCH_CYCLE_MODES(0x02),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
AncConfigType(final int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static AncConfigType fromCode(final int code) {
|
||||||
|
for (final AncConfigType param : AncConfigType.values()) {
|
||||||
|
if (param.code == code) {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/* 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.service.devices.oppo.commands;
|
||||||
|
|
||||||
|
import java.lang.Iterable;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum AncConfigValue {
|
||||||
|
OFF(0x01, "0"),
|
||||||
|
TRANSPARENCY(0x02, "2"),
|
||||||
|
ON(0x08, "1");
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String prefId;
|
||||||
|
|
||||||
|
AncConfigValue(final int code, final String prefId) {
|
||||||
|
this.code = code;
|
||||||
|
this.prefId = prefId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static AncConfigValue fromCode(final int code) {
|
||||||
|
for (final AncConfigValue param : AncConfigValue.values()) {
|
||||||
|
if (param.code == code) {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefId() {
|
||||||
|
return prefId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static AncConfigValue fromPrefId(final String prefId) {
|
||||||
|
for (final AncConfigValue param : AncConfigValue.values()) {
|
||||||
|
if (prefId.equals(param.prefId)) {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toMask(Iterable<AncConfigValue> modes) {
|
||||||
|
int mask = 0;
|
||||||
|
for (AncConfigValue mode : modes) {
|
||||||
|
mask |= mode.getCode();
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumSet<AncConfigValue> fromMask(int mask) {
|
||||||
|
EnumSet<AncConfigValue> modes = EnumSet.noneOf(AncConfigValue.class);
|
||||||
|
for (AncConfigValue mode : AncConfigValue.values()) {
|
||||||
|
if ((mask & mode.getCode()) == mode.getCode()) {
|
||||||
|
modes.add(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> toPrefIds(Iterable<AncConfigValue> modes) {
|
||||||
|
Set<String> prefIds = new HashSet<>();
|
||||||
|
for (AncConfigValue mode : modes) {
|
||||||
|
prefIds.add(mode.getPrefId());
|
||||||
|
}
|
||||||
|
return prefIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumSet<AncConfigValue> fromPrefIds(Iterable<String> prefIds) {
|
||||||
|
EnumSet<AncConfigValue> modes = EnumSet.noneOf(AncConfigValue.class);
|
||||||
|
for (String prefId : prefIds) {
|
||||||
|
AncConfigValue mode = fromPrefId(prefId);
|
||||||
|
if (mode != null) {
|
||||||
|
modes.add(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modes;
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/* 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.service.devices.oppo.commands;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum MiscConfigType {
|
||||||
|
LDAC(0x18),
|
||||||
|
GAME_MODE(0x06),
|
||||||
|
MULTIPOINT(0x11),
|
||||||
|
FIND_PHONE(0x26),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
MiscConfigType(final int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static MiscConfigType fromCode(final int code) {
|
||||||
|
for (final MiscConfigType param : MiscConfigType.values()) {
|
||||||
|
if (param.code == code) {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-1
@@ -21,7 +21,9 @@ import androidx.annotation.Nullable;
|
|||||||
public enum OppoCommand {
|
public enum OppoCommand {
|
||||||
BATTERY_REQ(0x0106),
|
BATTERY_REQ(0x0106),
|
||||||
BATTERY_RET(0x8106),
|
BATTERY_RET(0x8106),
|
||||||
DEVICE_INFO(0x0204),
|
SUBSCRIPTION_SET(0x0205),
|
||||||
|
SUBSCRIPTION_ACK(0x8205),
|
||||||
|
SUBSCRIPTION_RET(0x0204),
|
||||||
FIRMWARE_GET(0x0105),
|
FIRMWARE_GET(0x0105),
|
||||||
FIRMWARE_RET(0x8105),
|
FIRMWARE_RET(0x8105),
|
||||||
TOUCH_CONFIG_REQ(0x0108),
|
TOUCH_CONFIG_REQ(0x0108),
|
||||||
@@ -30,6 +32,14 @@ public enum OppoCommand {
|
|||||||
TOUCH_CONFIG_ACK(0x8401),
|
TOUCH_CONFIG_ACK(0x8401),
|
||||||
FIND_DEVICE_REQ(0x0400),
|
FIND_DEVICE_REQ(0x0400),
|
||||||
FIND_DEVICE_ACK(0x8400),
|
FIND_DEVICE_ACK(0x8400),
|
||||||
|
MISC_CONFIG_SET(0x0403),
|
||||||
|
MISC_CONFIG_REQ(0x010d),
|
||||||
|
MISC_CONFIG_ACK(0x8403),
|
||||||
|
MISC_CONFIG_RET(0x810d),
|
||||||
|
ANC_CONFIG_SET(0x0404),
|
||||||
|
ANC_CONFIG_REQ(0x010c),
|
||||||
|
ANC_CONFIG_ACK(0x8404),
|
||||||
|
ANC_CONFIG_RET(0x810c),
|
||||||
;
|
;
|
||||||
|
|
||||||
private final short code;
|
private final short code;
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/* 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.service.devices.oppo.commands;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum SubscriptionType {
|
||||||
|
BATTERY(0x01),
|
||||||
|
STATUS(0x02),
|
||||||
|
ANC_SELECTOR(0x03),
|
||||||
|
GAME_MODE(0x05),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
SubscriptionType(final int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static SubscriptionType fromCode(final int code) {
|
||||||
|
for (final SubscriptionType param : SubscriptionType.values()) {
|
||||||
|
if (param.code == code) {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4523,7 +4523,7 @@
|
|||||||
<item>@string/pref_title_touch_voice_assistant</item>
|
<item>@string/pref_title_touch_voice_assistant</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>
|
||||||
<item>@string/prefs_noise_control</item>
|
<item>@string/moondrop_touch_action_anc_mode</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="oppo_touch_tap_values">
|
<string-array name="oppo_touch_tap_values">
|
||||||
|
|||||||
@@ -3229,6 +3229,7 @@
|
|||||||
<string name="devicetype_realme_buds_t200" translatable="false">Realme Buds T200</string>
|
<string name="devicetype_realme_buds_t200" translatable="false">Realme Buds T200</string>
|
||||||
<string name="devicetype_realme_buds_t300" translatable="false">Realme Buds T300</string>
|
<string name="devicetype_realme_buds_t300" translatable="false">Realme Buds T300</string>
|
||||||
<string name="devicetype_realme_buds_air_5_pro" translatable="false">Realme Buds Air 5 Pro</string>
|
<string name="devicetype_realme_buds_air_5_pro" translatable="false">Realme Buds Air 5 Pro</string>
|
||||||
|
<string name="devicetype_realme_buds_air_6_pro" translatable="false">Realme Buds Air 6 Pro</string>
|
||||||
<string name="devicetype_galaxybuds" translatable="false">Galaxy Buds</string>
|
<string name="devicetype_galaxybuds" translatable="false">Galaxy Buds</string>
|
||||||
<string name="devicetype_galaxybuds_live" translatable="false">Galaxy Buds Live</string>
|
<string name="devicetype_galaxybuds_live" translatable="false">Galaxy Buds Live</string>
|
||||||
<string name="devicetype_galaxybuds_pro" translatable="false">Galaxy Buds Pro</string>
|
<string name="devicetype_galaxybuds_pro" translatable="false">Galaxy Buds Pro</string>
|
||||||
@@ -5042,4 +5043,5 @@
|
|||||||
<string name="pref_category_endurain" translatable="false">Endurain</string>
|
<string name="pref_category_endurain" translatable="false">Endurain</string>
|
||||||
<string name="pref_category_wanderer" translatable="false">Wanderer</string>
|
<string name="pref_category_wanderer" translatable="false">Wanderer</string>
|
||||||
<string name="activity_detail_upload_to_wanderer">Upload GPX to Wanderer</string>
|
<string name="activity_detail_upload_to_wanderer">Upload GPX to Wanderer</string>
|
||||||
|
<string name="select_at_least_option">Please select at least %1$d option</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="oppo_game_mode"
|
||||||
|
android:title="@string/prefs_game_mode"
|
||||||
|
android:icon="@drawable/ic_videogame"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:layout="@layout/preference_checkbox" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="oppo_multipoint"
|
||||||
|
android:title="@string/bluetooth_multipoint_pairing"
|
||||||
|
android:icon="@drawable/ic_bluetooth_searching"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:layout="@layout/preference_checkbox" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="oppo_touch_header_anc"
|
||||||
|
android:title="@string/prefs_active_noise_cancelling"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<MultiSelectListPreference
|
||||||
|
android:key="oppo_touch_anc_cycle_modes"
|
||||||
|
android:title="@string/prefs_noise_modes_to_cycle"
|
||||||
|
android:icon="@drawable/ic_sync"
|
||||||
|
android:entries="@array/onemore_noise_control_modes"
|
||||||
|
android:entryValues="@array/onemore_noise_control_values"
|
||||||
|
android:defaultValue="@array/onemore_noise_control_values" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -164,6 +164,7 @@ public class BluetoothNameTest extends TestBase {
|
|||||||
put("realme Buds T110", DeviceType.REALME_BUDS_T110);
|
put("realme Buds T110", DeviceType.REALME_BUDS_T110);
|
||||||
put("realme Buds T200", DeviceType.REALME_BUDS_T200); // #6258
|
put("realme Buds T200", DeviceType.REALME_BUDS_T200); // #6258
|
||||||
put("realme Buds T300", DeviceType.REALME_BUDS_T300); // #6258
|
put("realme Buds T300", DeviceType.REALME_BUDS_T300); // #6258
|
||||||
|
put("realme Buds Air6 Pro", DeviceType.REALME_BUDS_AIR_6_PRO);
|
||||||
put("Redmi Smart Band 2 604D", DeviceType.REDMISMARTBAND2); // #3274
|
put("Redmi Smart Band 2 604D", DeviceType.REDMISMARTBAND2); // #3274
|
||||||
put("vívosmart 5", DeviceType.GARMIN_VIVOSMART_5); // #3269
|
put("vívosmart 5", DeviceType.GARMIN_VIVOSMART_5); // #3269
|
||||||
put("Instinct Crossover", DeviceType.GARMIN_INSTINCT_CROSSOVER); // #3252
|
put("Instinct Crossover", DeviceType.GARMIN_INSTINCT_CROSSOVER); // #3252
|
||||||
|
|||||||
Reference in New Issue
Block a user