Huawei: Freebuds 6 initial support

This commit is contained in:
Me7c7
2025-06-09 20:28:41 +03:00
parent 894016dbe0
commit 27804ff485
13 changed files with 316 additions and 2 deletions
@@ -348,6 +348,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_HUAWEI_FREEBUDS_AUDIOMODE = "pref_freebuds_audiomode";
public static final String PREF_HUAWEI_FREEBUDS_ANC_MODE = "pref_freebuds_anc_mode";
public static final String PREF_HUAWEI_FREEBUDS_VOICE_BOOST = "pref_freebuds_voice_boost";
public static final String PREF_HUAWEI_FREEBUDS_BETTER_AUDIO_QUALITY = "pref_freebuds_better_audio_quality";
public static final String PREF_GALAXY_BUDS_AMBIENT_MODE = "pref_galaxy_buds_ambient_mode";
public static final String PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS = "pref_galaxy_buds_ambient_voice_focus";
@@ -654,6 +654,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_AUDIOMODE);
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_ANC_MODE);
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_VOICE_BOOST);
addPreferenceHandlerFor(PREF_HUAWEI_FREEBUDS_BETTER_AUDIO_QUALITY);
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOICE_FOCUS);
addPreferenceHandlerFor(PREF_GALAXY_BUDS_AMBIENT_VOLUME);
@@ -139,6 +139,9 @@ public abstract class HuaweiFreebudsCoordinator extends AbstractBLClassicDeviceC
&& supports(device, HuaweiHeadphonesCapabilities.VoiceBoost)) {
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_huawei_headphones_voice_boost);
}
if (supports(device, HuaweiHeadphonesCapabilities.BetterAudioQuality)) {
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_huawei_headphones_better_audio_quality);
}
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_headphones);
return deviceSpecificSettings;
}
@@ -21,5 +21,6 @@ public enum HuaweiHeadphonesCapabilities {
InEarDetection,
AudioModes,
NoiseCancellationModes,
VoiceBoost
VoiceBoost,
BetterAudioQuality
}
@@ -687,6 +687,10 @@ public class HuaweiPacket {
return new Earphones.InEarStateResponse(paramsProvider).fromPacket(this);
case Earphones.GetAudioModeRequest.id:
return new Earphones.GetAudioModeRequest.Response(paramsProvider).fromPacket(this);
case Earphones.SetBetterAudioQuality.id:
return new Earphones.SetBetterAudioQuality.Response(paramsProvider).fromPacket(this);
case Earphones.GetBetterAudioQuality.id:
return new Earphones.GetBetterAudioQuality.Response(paramsProvider).fromPacket(this);
}
case FileDownloadService2C.id:
switch (this.commandId) {
@@ -0,0 +1,65 @@
/* Copyright (C) 2025 Me7c7
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.huawei.freebuds6;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiFreebudsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHeadphonesCapabilities;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFreebudsSupport;
public class HuaweiFreebuds6Coordinator extends HuaweiFreebudsCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("huawei freebuds 6", Pattern.CASE_INSENSITIVE);
}
@Override
public Set<HuaweiHeadphonesCapabilities> getCapabilities() {
return new HashSet<>(Arrays.asList(
HuaweiHeadphonesCapabilities.InEarDetection,
HuaweiHeadphonesCapabilities.BetterAudioQuality
));
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass(final GBDevice device) {
return HuaweiFreebudsSupport.class;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.HUAWEI_FREEBUDS6;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_huawei_freebuds_6;
}
}
@@ -21,6 +21,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAudioModeRequest.AudioMode;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetANCModeRequest.ANCMode;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetBetterAudioQualityRequest.AudioQualityMode;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetVoiceBoostRequest.VoiceBoostMode;
// Information from:
@@ -151,4 +152,65 @@ public class Earphones {
}
}
}
public static class SetBetterAudioQuality {
public static final byte id = (byte) 0xa2;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider, AudioQualityMode audioQualityMode) {
super(paramsProvider);
this.serviceId = Earphones.id;
this.commandId = id;
this.complete = true;
this.tlv = new HuaweiTLV().put(0x01, audioQualityMode.getCode());
}
}
public static class Response extends HuaweiPacket {
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = Earphones.id;
this.commandId = id;
this.complete = true;
}
@Override
public void parseTlv() throws ParseException {
// TODO:
}
}
}
public static class GetBetterAudioQuality {
public static final byte id = (byte) 0xa3;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = Earphones.id;
this.commandId = id;
this.complete = true;
this.tlv = new HuaweiTLV();
}
}
// TODO: this response can be async too.
public static class Response extends HuaweiPacket {
public AudioQualityMode state;
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = Earphones.id;
this.commandId = id;
this.complete = true;
}
@Override
public void parseTlv() throws ParseException {
this.state = AudioQualityMode.fromBoolean(this.tlv.getByte(0x02, (byte) 0) == 1);
}
}
}
}
@@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.Ga
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.forerunner.GarminForerunner645MusicCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.GarminVivosmart4Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.watches.AmazfitActive2NfcCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.freebuds6.HuaweiFreebuds6Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatch5.HuaweiWatch5Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.huaweiwatchfit4pro.HuaweiWatchFit4ProCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.oppo.OppoEncoBuds2Coordinator;
@@ -644,6 +645,7 @@ public enum DeviceType {
HUAWEIWATCH5(HuaweiWatch5Coordinator.class),
HUAWEI_FREEBUDS5I(HuaweiFreebuds5iCoordinator.class),
HUAWEI_FREEBUDS_PRO(HuaweiFreebudsProCoordinator.class),
HUAWEI_FREEBUDS6(HuaweiFreebuds6Coordinator.class),
VESC(VescCoordinator.class),
BINARY_SENSOR(BinarySensorCoordinator.class),
FLIPPER_ZERO(FlipperZeroCoordinator.class),
@@ -30,15 +30,19 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiFreebudsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHeadphonesCapabilities;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.HeadphoneHelper;
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBetterAudioQualityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetProductInformationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetANCModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAudioModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetBetterAudioQualityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetVoiceBoostRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetPauseWhenRemovedFromEarRequest;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -61,9 +65,23 @@ public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneH
headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
}
protected void initializeDeviceConfigure() {
try {
HuaweiFreebudsCoordinator coordinator = (HuaweiFreebudsCoordinator) this.gbDevice.getDeviceCoordinator();
if (coordinator.supports(this.gbDevice, HuaweiHeadphonesCapabilities.BetterAudioQuality)) {
GetBetterAudioQualityRequest req = new GetBetterAudioQualityRequest(super.getSupportProvider());
req.doPerform();
}
} catch (IOException e) {
GB.toast(this.getContext(), "Final initialization of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Final initialization of Huawei device failed", e);
}
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Huawei Freebuds init" );
LOG.info("Huawei Freebuds init");
super.getSupportProvider().setup(getDevice(), getContext());
@@ -76,6 +94,7 @@ public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneH
public void call() {
// This also (optionally) starts the battery polling
getSupportProvider().getBatteryLevel();
initializeDeviceConfigure();
}
});
deviceProductReq.doPerform();
@@ -133,6 +152,9 @@ public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneH
case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_VOICE_BOOST:
new SetVoiceBoostRequest(getSupportProvider()).doPerform();
break;
case DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_BETTER_AUDIO_QUALITY:
new SetBetterAudioQualityRequest(getSupportProvider()).doPerform();
break;
case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
if (!GBApplication.getDevicePrefs(gbDevice).getBatteryPollingEnabled()) {
getSupportProvider().stopBatteryRunnerDelayed();
@@ -0,0 +1,65 @@
/* Copyright (C) 2025 Me7c7
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.huawei.requests;
import android.content.SharedPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
public class GetBetterAudioQualityRequest extends Request {
private static final Logger LOG = LoggerFactory.getLogger(GetBetterAudioQualityRequest.class);
public GetBetterAudioQualityRequest(HuaweiSupportProvider support) {
super(support);
this.serviceId = Earphones.id;
this.commandId = Earphones.GetBetterAudioQuality.id;
}
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
try {
return new Earphones.GetBetterAudioQuality.Request(paramsProvider).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new Request.RequestCreationException(e);
}
}
@Override
protected void processResponse() throws Request.ResponseParseException {
LOG.debug("handle GetBetterAudioQuality");
if (!(receivedPacket instanceof Earphones.GetBetterAudioQuality.Response))
throw new Request.ResponseTypeMismatchException(receivedPacket,Earphones.GetBetterAudioQuality.Response.class);
Earphones.GetBetterAudioQuality.Response packet = (Earphones.GetBetterAudioQuality.Response) receivedPacket;
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_BETTER_AUDIO_QUALITY, SetBetterAudioQualityRequest.AudioQualityMode.toBoolean(packet.state));
editor.apply();
}
}
@@ -0,0 +1,75 @@
/* Copyright (C) 2025 Me7c7
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.huawei.requests;
import android.content.SharedPreferences;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Earphones;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
public class SetBetterAudioQualityRequest extends Request {
public enum AudioQualityMode {
OFF((byte) 0x00),
ON((byte) 0x01);
private final byte code;
AudioQualityMode(final byte code) {
this.code = code;
}
public byte getCode() {
return this.code;
}
public static SetBetterAudioQualityRequest.AudioQualityMode fromBoolean(boolean state) {
return state ? ON : OFF;
}
public static boolean toBoolean(SetBetterAudioQualityRequest.AudioQualityMode mode) {
return mode == ON;
}
public static SetBetterAudioQualityRequest.AudioQualityMode fromPreferences(final SharedPreferences prefs) {
return SetBetterAudioQualityRequest.AudioQualityMode.fromBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HUAWEI_FREEBUDS_BETTER_AUDIO_QUALITY, toBoolean(OFF)));
}
}
public SetBetterAudioQualityRequest(HuaweiSupportProvider supportProvider) {
super(supportProvider);
this.serviceId = Earphones.id;
this.commandId = Earphones.SetBetterAudioQuality.id;
}
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
SetBetterAudioQualityRequest.AudioQualityMode audioQualityMode = SetBetterAudioQualityRequest.AudioQualityMode.fromPreferences(GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()));
return new Earphones.SetBetterAudioQuality.Request(this.paramsProvider, audioQualityMode).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}
}
}
+3
View File
@@ -1986,6 +1986,7 @@
<string name="devicetype_huawei_watch5">Huawei Watch 5</string>
<string name="devicetype_huawei_freebuds_5i">Huawei FreeBuds 5i</string>
<string name="devicetype_huawei_freebuds_pro">Huawei FreeBuds Pro</string>
<string name="devicetype_huawei_freebuds_6">Huawei FreeBuds 6</string>
<string name="devicetype_femometer_vinca2">Femometer Vinca II</string>
<string name="devicetype_xiaomi_watch_lite">Xiaomi Watch Lite</string>
<string name="devicetype_redmiwatch3active">Redmi Watch 3 Active</string>
@@ -3946,6 +3947,8 @@
<string name="banglejs_notification_missed_call_source">Incoming call</string>
<string name="banglejs_notification_missed_call_suppressed_caller_id">Unknown caller. ID is suppressed</string>
<string name="huawei_voice_boost">Voice Boost</string>
<string name="huawei_better_audio_quality_title">Prioritise better audio quality.</string>
<string name="huawei_better_audio_quality_summary">Enable better audio codecs (LDAC). This should improve sound quality, but it increases power consumption and can cause delays.</string>
<string name="maps">Maps</string>
<string name="maps_settings">Maps settings</string>
<string name="maps_download_title">Download Maps</string>
@@ -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:defaultValue="false"
android:key="pref_freebuds_better_audio_quality"
android:layout="@layout/preference_checkbox"
android:title="@string/huawei_better_audio_quality_title"
android:summary="@string/huawei_better_audio_quality_summary" />
</androidx.preference.PreferenceScreen>