diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/earfun/EarFunFreePro3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/earfun/EarFunFreePro3Coordinator.java
new file mode 100644
index 0000000000..8a62d5de61
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/earfun/EarFunFreePro3Coordinator.java
@@ -0,0 +1,120 @@
+/* Copyright (C) 2026 Obside
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.earfun;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.freepro3.EarFunFreePro3DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.freepro3.EarFunFreePro3SettingsCustomizer;
+
+public class EarFunFreePro3Coordinator extends AbstractEarFunCoordinator {
+ @Override
+ public int getDeviceNameResource() {
+ return R.string.devicetype_earfun_free_pro_3;
+ }
+
+ @Override
+ public boolean supports(GBDeviceCandidate candidate) {
+ if (candidate.getName().startsWith("EarFun Free Pro 3")) {
+ return true;
+ }
+
+ // fallback: check UUIDs and MAC prefix to detect even if device name was changed
+ // Free Pro 3 has 9 UUIDs: 5 standard audio + 4 vendor (EB04-EB07)
+ // Same MAC prefix (70:5A:6F) as Air Pro 4, so we must distinguish by UUID set
+ String[] uuids = {
+ "00001101-0000-1000-8000-00805f9b34fb",
+ "0000111e-0000-1000-8000-00805f9b34fb",
+ "0000110b-0000-1000-8000-00805f9b34fb",
+ "0000110c-0000-1000-8000-00805f9b34fb",
+ "0000110e-0000-1000-8000-00805f9b34fb",
+ "0000eb04-d102-11e1-9b23-00025b00a5a5",
+ "0000eb06-d102-11e1-9b23-00025b00a5a5",
+ "0000eb07-d102-11e1-9b23-00025b00a5a5",
+ "0000eb05-d102-11e1-9b23-00025b00a5a5"};
+
+ boolean allServicesSupported = Arrays.stream(uuids)
+ .map(UUID::fromString)
+ .map(candidate::supportsService).allMatch(b -> b);
+
+ // Must NOT have Air Pro 4 extras (df21fe2c, 180f battery, 180a device info)
+ boolean hasAirPro4Extras = candidate.supportsService(
+ UUID.fromString("df21fe2c-2515-4fdb-8886-f12c4d67927c"));
+
+ boolean macAddressMatches = candidate.getMacAddress().toUpperCase().startsWith("70:5A:6F");
+
+ return allServicesSupported && !hasAirPro4Extras && macAddressMatches;
+ }
+
+ @NonNull
+ @Override
+ public Class extends DeviceSupport> getDeviceSupportClass(final GBDevice device) {
+ return EarFunFreePro3DeviceSupport.class;
+ }
+
+ @Override
+ public int getBatteryCount(final GBDevice device) {
+ return 3;
+ }
+
+ @Override
+ public BatteryConfig[] getBatteryConfig(final GBDevice device) {
+ BatteryConfig battery1 = new BatteryConfig(2, R.drawable.ic_buds_pro_case, R.string.battery_case);
+ BatteryConfig battery2 = new BatteryConfig(0, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
+ BatteryConfig battery3 = new BatteryConfig(1, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
+ return new BatteryConfig[]{battery1, battery2, battery3};
+ }
+
+ @Override
+ public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
+ final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
+ // Category Audio Experience
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_header_audio_experience);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_10_band_equalizer);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_free_pro_3_sound_control);
+
+ // Category Audio Quality & Connectivity
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_header_connectivity);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_free_pro_3_audio_quality);
+
+ // Category System Settings
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_header_system_settings);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_free_pro_3_gestures);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_find_device);
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_earfun_device_name);
+ final List callsAndNotif = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
+ callsAndNotif.add(R.xml.devicesettings_headphones);
+ return deviceSpecificSettings;
+ }
+
+ @Override
+ public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+ return new EarFunFreePro3SettingsCustomizer(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index 35b35e3b0c..78e6780de0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -54,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.coordinator.C
import nodomain.freeyourgadget.gadgetbridge.devices.divoom.PixooCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.earfun.EarFunAirPro4Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.earfun.EarFunFreePro3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.earfun.EarFunAirSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.evenrealities.G1DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.femometer.FemometerVinca2DeviceCoordinator;
@@ -969,6 +970,7 @@ public enum DeviceType {
B_AND_W_P_SERIES(BandWPSeriesDeviceCoordinator.class),
EARFUN_AIR_S(EarFunAirSCoordinator.class),
EARFUN_AIR_PRO_4(EarFunAirPro4Coordinator.class),
+ EARFUN_FREE_PRO_3(EarFunFreePro3Coordinator.class),
SCANNABLE(ScannableDeviceCoordinator.class),
CYCLING_SENSOR(CyclingSensorCoordinator.class),
BLE_GATT_CLIENT(BleGattClientCoordinator.class),
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3DeviceSupport.java
new file mode 100644
index 0000000000..9fe8b01aee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3DeviceSupport.java
@@ -0,0 +1,27 @@
+/* Copyright (C) 2026 Obside
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.freepro3;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunDeviceSupport;
+
+public class EarFunFreePro3DeviceSupport extends EarFunDeviceSupport {
+
+ @Override
+ protected EarFunFreePro3Protocol createDeviceProtocol() {
+ return new EarFunFreePro3Protocol(getDevice());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3Protocol.java
new file mode 100644
index 0000000000..fe3396bbc2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3Protocol.java
@@ -0,0 +1,64 @@
+/* Copyright (C) 2026 Obside
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.freepro3;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunPacketEncoder.joinPackets;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunPacketEncoder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunProtocol;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class EarFunFreePro3Protocol extends EarFunProtocol {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EarFunFreePro3Protocol.class);
+
+ @Override
+ public byte[] encodeTestNewFunction(@Nullable Bundle options) {
+ return joinPackets(
+ // new EarFunPacket(EarFunPacket.Command.COMMAND_REBOOT).encode()
+ );
+ }
+
+ @Override
+ public byte[] encodeSendConfiguration(String config) {
+ if (Equalizer.containsKey(Equalizer.TenBandEqualizer, config)) {
+ Prefs prefs = getDevicePrefs();
+ return EarFunPacketEncoder.encodeSetEqualizerTenBands(prefs);
+ }
+ return super.encodeSendConfiguration(config);
+ }
+
+ @Override
+ public byte[] encodeSettingsReq() {
+ return EarFunPacketEncoder.encodeAirPro4SettingsReq();
+ }
+
+ protected EarFunFreePro3Protocol(GBDevice device) {
+ super(device);
+ DeviceType type = device.getType();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3SettingsCustomizer.java
new file mode 100644
index 0000000000..f1fd52106c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/earfun/freepro3/EarFunFreePro3SettingsCustomizer.java
@@ -0,0 +1,95 @@
+/* Copyright (C) 2026 Obside
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.freepro3;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_AMBIENT_SOUND_CONTROL;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_ANC_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_EQUALIZER_PRESET;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.EarFunSettingsPreferenceConst.PREF_EARFUN_TRANSPARENCY_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer.TenBandEqualizerPresets;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.EarFunSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.earfun.prefs.Equalizer;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class EarFunFreePro3SettingsCustomizer extends EarFunSettingsCustomizer {
+
+ @Override
+ public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
+ super.onPreferenceChange(preference, handler);
+ String key = preference.getKey();
+ if (key == null) {
+ return;
+ }
+ switch (key) {
+ case PREF_EARFUN_AMBIENT_SOUND_CONTROL:
+ onPreferenceChangeAmbientSoundControl(handler);
+ break;
+ case PREF_EARFUN_EQUALIZER_PRESET:
+ onPreferenceChangeEqualizerPreset(handler, Equalizer.TenBandEqualizer, TenBandEqualizerPresets);
+ break;
+ }
+ // if the band sliders match a preset, update the preset list
+ if (Equalizer.containsKey(Equalizer.TenBandEqualizer, key)) {
+ int equalizerPreset = getSelectedPresetFromEqualizerBands(handler,
+ Equalizer.TenBandEqualizer, TenBandEqualizerPresets);
+ ListPreference listPreferenceEqualizerPreset = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
+ if (listPreferenceEqualizerPreset != null) {
+ listPreferenceEqualizerPreset.setValue(Integer.toString(equalizerPreset));
+ }
+ }
+ }
+
+ @Override
+ public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
+ super.customizeSettings(handler, prefs, rootKey);
+ initializeEqualizerPresetListPreference(handler, TenBandEqualizerPresets);
+ }
+
+ private void onPreferenceChangeAmbientSoundControl(DeviceSpecificSettingsHandler handler) {
+ ListPreference listPreferenceAmbientSound = handler.findPreference(PREF_EARFUN_AMBIENT_SOUND_CONTROL);
+ ListPreference listPreferenceTransparencyMode = handler.findPreference(PREF_EARFUN_TRANSPARENCY_MODE);
+ ListPreference listPreferenceAncMode = handler.findPreference(PREF_EARFUN_ANC_MODE);
+
+ if (listPreferenceAmbientSound == null || listPreferenceTransparencyMode == null || listPreferenceAncMode == null) {
+ return;
+ }
+
+ switch (listPreferenceAmbientSound.getValue()) {
+ case "1": // noise cancelling
+ listPreferenceTransparencyMode.setVisible(false);
+ listPreferenceAncMode.setVisible(true);
+ break;
+ case "2": // transparency
+ listPreferenceTransparencyMode.setVisible(true);
+ listPreferenceAncMode.setVisible(false);
+ break;
+ default:
+ listPreferenceTransparencyMode.setVisible(false);
+ listPreferenceAncMode.setVisible(false);
+ }
+ }
+
+ public EarFunFreePro3SettingsCustomizer(final GBDevice device) {
+ super(device);
+ }
+}
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 071f45c6d5..34cc1d25af 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -4171,6 +4171,18 @@
- 8
+
+ - @string/pref_title_stable_connection
+ - aptX
+ - aptX Adaptive
+
+
+
+ - 0
+ - 1
+ - 3
+
+
- Google Fast Pair
- LE Audio
@@ -4251,6 +4263,38 @@
- 13
+
+ - @string/none
+ - @string/pref_media_volumeup
+ - @string/pref_media_volumedown
+ - @string/pref_media_next
+ - @string/pref_media_previous
+ - @string/pref_media_playpause
+ - @string/prefs_game_mode
+ - @string/pref_title_touch_voice_assistant
+ - @string/earfun_tap_action_noise_control_all_modes
+ - @string/earfun_tap_action_noise_control_normal_wind
+ - @string/earfun_tap_action_noise_control_normal_ambient
+ - @string/earfun_tap_action_noise_control_wind_ambient
+ - @string/earfun_tap_action_redial
+
+
+
+ - 16
+ - 2
+ - 3
+ - 5
+ - 4
+ - 1
+ - 7
+ - 6
+ - 8
+ - 9
+ - 10
+ - 11
+ - 13
+
+
- @string/sony_ambient_sound_off
- @string/sony_ambient_sound_noise_cancelling
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 78c338ca10..8a7ab54857 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2281,6 +2281,7 @@
Bowers and Wilkins P series
EarFun Air S
EarFun Air Pro 4
+ EarFun Free Pro 3
ATC_BLE_OEPL
iGPSPORT BSC200
iGPSPORT BSC200S
diff --git a/app/src/main/res/xml/devicesettings_earfun_free_pro_3_audio_quality.xml b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_audio_quality.xml
new file mode 100644
index 0000000000..93bd3a8c7c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_audio_quality.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_earfun_free_pro_3_gestures.xml b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_gestures.xml
new file mode 100644
index 0000000000..ab5df603e5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_gestures.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_earfun_free_pro_3_sound_control.xml b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_sound_control.xml
new file mode 100644
index 0000000000..a77f578471
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_earfun_free_pro_3_sound_control.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+