diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
index eb5a3fb13..2b6cbbf0d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -109,6 +109,35 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_GALAXY_BUDS_TOUCH_LEFT = "pref_galaxy_buds_touch_left";
public static final String PREF_GALAXY_BUDS_TOUCH_RIGHT = "pref_galaxy_buds_touch_right";
+ public static final String PREF_SONY_WH1000XM3_AMBIENT_SOUND_CONTROL = "pref_sony_wh1000xm3_ambient_sound_control";
+ public static final String PREF_SONY_WH1000XM3_FOCUS_VOICE = "pref_sony_wh1000xm3_focus_voice";
+ public static final String PREF_SONY_WH1000XM3_AMBIENT_SOUND_LEVEL = "pref_sony_wh1000xm3_ambient_sound_level";
+ public static final String PREF_SONY_WH1000XM3_SOUND_POSITION = "pref_sony_wh1000xm3_sound_position";
+ public static final String PREF_SONY_WH1000XM3_SURROUND_MODE = "pref_sony_wh1000xm3_surround_mode";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MODE = "pref_sony_wh1000xm3_equalizer_mode";
+ public static final String PREF_SONY_WH1000XM3_DSEE_HX = "pref_sony_wh1000xm3_dsee_hx";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_400 = "pref_sony_wh1000xm3_equalizer_manual_band_400";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_1000 = "pref_sony_wh1000xm3_equalizer_manual_band_1000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_2500 = "pref_sony_wh1000xm3_equalizer_manual_band_2500";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_6300 = "pref_sony_wh1000xm3_equalizer_manual_band_6300";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_16000 = "pref_sony_wh1000xm3_equalizer_manual_band_16000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_CLEAR_BASS = "pref_sony_wh1000xm3_equalizer_manual_clear_bass";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_400 = "pref_sony_wh1000xm3_equalizer_custom_1_band_400";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_1000 = "pref_sony_wh1000xm3_equalizer_custom_1_band_1000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_2500 = "pref_sony_wh1000xm3_equalizer_custom_1_band_2500";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_6300 = "pref_sony_wh1000xm3_equalizer_custom_1_band_6300";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_16000 = "pref_sony_wh1000xm3_equalizer_custom_1_band_16000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_CLEAR_BASS = "pref_sony_wh1000xm3_equalizer_custom_1_clear_bass";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_400 = "pref_sony_wh1000xm3_equalizer_custom_2_band_400";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_1000 = "pref_sony_wh1000xm3_equalizer_custom_2_band_1000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_2500 = "pref_sony_wh1000xm3_equalizer_custom_2_band_2500";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_6300 = "pref_sony_wh1000xm3_equalizer_custom_2_band_6300";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_16000 = "pref_sony_wh1000xm3_equalizer_custom_2_band_16000";
+ public static final String PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_CLEAR_BASS = "pref_sony_wh1000xm3_equalizer_custom_2_clear_bass";
+ public static final String PREF_SONY_WH1000XM3_TOUCH_SENSOR = "pref_sony_wh1000xm3_touch_sensor";
+ public static final String PREF_SONY_WH1000XM3_AUTOMATIC_POWER_OFF = "pref_sony_wh1000xm3_automatic_power_off";
+ public static final String PREF_SONY_WH1000XM3_NOTIFICATION_VOICE_GUIDE = "pref_sony_wh1000xm3_notification_voice_guide";
+
public static final String PREF_SOUNDS = "sounds";
public static final String PREF_AUTH_KEY = "authkey";
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
index 9a52525fa..deb94e939 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -119,6 +119,34 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_INEAR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_CONTROL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_FOCUS_VOICE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_LEVEL;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SOUND_POSITION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SURROUND_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_400;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_1000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_2500;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_6300;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_16000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_CLEAR_BASS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_400;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_1000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_2500;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_6300;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_16000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_CLEAR_BASS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_400;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_1000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_2500;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_6300;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_16000;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_CLEAR_BASS;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_DSEE_HX;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_TOUCH_SENSOR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AUTOMATIC_POWER_OFF;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_NOTIFICATION_VOICE_GUIDE;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION;
@@ -485,6 +513,35 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_LEFT);
addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_RIGHT);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_AMBIENT_SOUND_CONTROL);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_FOCUS_VOICE);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_AMBIENT_SOUND_LEVEL);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_SOUND_POSITION);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_SURROUND_MODE);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MODE);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_400);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_1000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_2500);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_6300);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_16000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_CLEAR_BASS);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_400);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_1000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_2500);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_6300);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_16000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_CLEAR_BASS);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_400);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_1000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_2500);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_6300);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_16000);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_CLEAR_BASS);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_DSEE_HX);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_TOUCH_SENSOR);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_AUTOMATIC_POWER_OFF);
+ addPreferenceHandlerFor(PREF_SONY_WH1000XM3_NOTIFICATION_VOICE_GUIDE);
+
String sleepTimeState = prefs.getString(PREF_SLEEP_TIME, PREF_DO_NOT_DISTURB_OFF);
boolean sleepTimeScheduled = sleepTimeState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AmbientSoundControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AmbientSoundControl.java
new file mode 100644
index 000000000..e1d4900a9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AmbientSoundControl.java
@@ -0,0 +1,24 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+public enum AmbientSoundControl {
+ OFF,
+ NOISE_CANCELLING,
+ WIND_NOISE_REDUCTION,
+ AMBIENT_SOUND
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AutomaticPowerOff.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AutomaticPowerOff.java
new file mode 100644
index 000000000..c70f33094
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/AutomaticPowerOff.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+public enum AutomaticPowerOff {
+ OFF,
+ AFTER_5_MIN,
+ AFTER_30_MIN,
+ AFTER_1_HOUR,
+ AFTER_3_HOUR
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerCustomBands.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerCustomBands.java
new file mode 100644
index 000000000..d7b73ffa3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerCustomBands.java
@@ -0,0 +1,51 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+import java.util.List;
+
+public class EqualizerCustomBands {
+ private List bands;
+ private int clearBass;
+
+ public EqualizerCustomBands(List bands, int clearBass) {
+ if (bands.size() != 5) {
+ throw new IllegalArgumentException("Equalizer needs exactly 5 bands");
+ }
+
+ for (Integer band : bands) {
+ if (band < -10 || band > 10) {
+ throw new IllegalArgumentException(String.format("Bands should be between -10 and 10, got %d", band));
+ }
+ }
+
+ if (clearBass < -10 || clearBass > 10) {
+ throw new IllegalArgumentException(String.format("Clear Bass value shoulud be between -10 and 10, got %d", clearBass));
+ }
+
+ this.bands = bands;
+ this.clearBass = clearBass;
+ }
+
+ public List getBands() {
+ return bands;
+ }
+
+ public int getClearBass() {
+ return clearBass;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerPreset.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerPreset.java
new file mode 100644
index 000000000..f597c3a33
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/EqualizerPreset.java
@@ -0,0 +1,32 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+public enum EqualizerPreset {
+ OFF,
+ BRIGHT,
+ EXCITED,
+ MELLOW,
+ RELAXED,
+ VOCAL,
+ TREBLE_BOOST,
+ BASS_BOOST,
+ SPEECH,
+ MANUAL,
+ CUSTOM_1,
+ CUSTOM_2
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SonyWh1000Xm3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SonyWh1000Xm3Coordinator.java
new file mode 100644
index 000000000..852cac37e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SonyWh1000Xm3Coordinator.java
@@ -0,0 +1,149 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class SonyWh1000Xm3Coordinator extends AbstractDeviceCoordinator {
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWh1000Xm3Coordinator.class);
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ if (candidate.getName().contains("WH-1000XM3")) {
+ return DeviceType.SONY_WH_1000XM3;
+ }
+ return DeviceType.UNKNOWN;
+ }
+
+ @Override
+ public DeviceType getDeviceType() {
+ return DeviceType.SONY_WH_1000XM3;
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Sony";
+ }
+
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+ }
+
+ @Nullable
+ @Override
+ public Class extends Activity> getPairingActivity() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsActivityDataFetching() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsActivityTracking() {
+ return false;
+ }
+
+ @Override
+ public SampleProvider extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+ return null;
+ }
+
+ @Override
+ public InstallHandler findInstallHandler(Uri uri, Context context) {
+ return null;
+ }
+
+ @Override
+ public boolean supportsScreenshots() {
+ return false;
+ }
+
+ @Override
+ public int getAlarmSlotCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean supportsSmartWakeup(GBDevice device) {
+ return false;
+ }
+
+ @Override
+ public boolean supportsHeartRateMeasurement(GBDevice device) {
+ return false;
+ }
+
+ @Override
+ public boolean supportsAppsManagement() {
+ return false;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsCalendarEvents() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsRealtimeData() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsWeather() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsFindDevice() {
+ return false;
+ }
+
+ @Override
+ public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
+ return new int[]{
+ R.xml.devicesettings_sony_1000xm3
+ };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SoundPosition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SoundPosition.java
new file mode 100644
index 000000000..650afdab7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SoundPosition.java
@@ -0,0 +1,26 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+public enum SoundPosition {
+ OFF,
+ FRONT,
+ FRONT_LEFT,
+ FRONT_RIGHT,
+ REAR_LEFT,
+ REAR_RIGHT
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SurroundMode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SurroundMode.java
new file mode 100644
index 000000000..92e9b8240
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wh1000xm3/SurroundMode.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3;
+
+public enum SurroundMode {
+ OFF,
+ ARENA,
+ CLUB,
+ OUTDOOR_STAGE,
+ CONCERT_HALL
+}
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 01d60fe27..ced968da1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -102,6 +102,7 @@ public enum DeviceType {
DOMYOS_T540(400, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_domyos_t540),
NOTHING_EAR1(410, R.drawable.ic_device_nothingear, R.drawable.ic_device_nothingear_disabled, R.string.devicetype_nothingear1),
GALAXY_BUDS(420, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_galaxybuds),
+ SONY_WH_1000XM3(430, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_sony_wh_1000xm3),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
index ad8e5064e..275b489fd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
@@ -94,6 +94,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Sup
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wh1000xm3.SonyWh1000Xm3Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -366,7 +367,9 @@ public class DeviceSupportFactory {
case GALAXY_BUDS:
deviceSupport = new ServiceDeviceSupport(new GalaxyBudsDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
-
+ case SONY_WH_1000XM3:
+ deviceSupport = new ServiceDeviceSupport(new SonyWh1000Xm3Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
+ break;
}
if (deviceSupport != null) {
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java
index 55ae751f5..6db2d9832 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btclassic/BtClassicIoThread.java
@@ -161,7 +161,7 @@ public abstract class BtClassicIoThread extends GBDeviceIoThread {
mOutStream = mBtSocket.getOutputStream();
setUpdateState(GBDevice.State.CONNECTED);
} catch (IOException e) {
- LOG.error("Server socket cannot be started.");
+ LOG.error("Server socket cannot be started.", e);
//LOG.error(e.getMessage());
setUpdateState(originalState);
mInStream = null;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3IoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3IoThread.java
new file mode 100644
index 000000000..823f7bf63
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3IoThread.java
@@ -0,0 +1,74 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wh1000xm3;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyWh1000Xm3IoThread extends BtClassicIoThread {
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWh1000Xm3IoThread.class);
+
+ public SonyWh1000Xm3IoThread(GBDevice gbDevice, Context context, SonyWh1000Xm3Protocol xm3protocol, SonyWh1000Xm3Support xm3support, BluetoothAdapter roidmiBtAdapter) {
+ super(gbDevice, context, xm3protocol, xm3support, roidmiBtAdapter);
+ }
+
+ @Override
+ protected byte[] parseIncoming(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
+ byte[] incoming = new byte[1];
+
+ while (true) {
+ inputStream.read(incoming);
+
+ if (incoming[0] == SonyWh1000Xm3Protocol.PACKET_HEADER) {
+ msgStream.reset();
+ continue;
+ }
+
+ if (incoming[0] == SonyWh1000Xm3Protocol.PACKET_TRAILER) {
+ break;
+ }
+
+ msgStream.write(incoming);
+ }
+
+ byte[] msgArray = msgStream.toByteArray();
+ LOG.debug("Received: " + GB.hexdump(msgArray, 0, msgArray.length));
+ return msgArray;
+ }
+
+ @NonNull
+ @Override
+ protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
+ return UUID.fromString("96CC203E-5068-46ad-B32D-E316F5E069BA");
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Protocol.java
new file mode 100644
index 000000000..8d60a19e0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Protocol.java
@@ -0,0 +1,585 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wh1000xm3;
+
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.AmbientSoundControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.AutomaticPowerOff;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.EqualizerCustomBands;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.EqualizerPreset;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.SoundPosition;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.SurroundMode;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class SonyWh1000Xm3Protocol extends GBDeviceProtocol {
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWh1000Xm3Protocol.class);
+
+ /**
+ * Packet format:
+ *
+ * - PACKET_HEADER
+ * - Command type? - almost always 0x0c?
+ * - Sequence Number - needs to be updated with the one sent in the responses
+ * - 4-byte big endian int with number of bytes that will follow
+ * - N bytes of data
+ * - Checksum (1-byte sum, excluding header)
+ * - PACKET_TRAILER
+ *
+ * Data between PACKET_HEADER and PACKET_TRAILER is escaped with PACKET_ESCAPE, and the
+ * following byte masked with PACKET_ESCAPE_MASK.
+ */
+
+ public static final byte PACKET_HEADER = 0x3e;
+ public static final byte PACKET_TRAILER = 0x3c;
+ public static final byte PACKET_ESCAPE = 0x3d;
+ public static final byte PACKET_ESCAPE_MASK = (byte) 0b11101111;
+
+ private static final byte MSG_TYPE_ACK = 0x01;
+
+ private static final byte CMD_SOUND_SURROUND = 0x01;
+ private static final byte CMD_SOUND_POSITION = 0x02;
+
+ private int sequenceNumber = 0;
+
+ public SonyWh1000Xm3Protocol(GBDevice device) {
+ super(device);
+ }
+
+ @Override
+ public GBDeviceEvent[] decodeResponse(byte[] res) {
+ byte[] message = unescape(res);
+ String hexdump = GB.hexdump(message, 0, message.length);
+
+ LOG.debug("Received {}", hexdump);
+
+ byte messageChecksum = message[message.length - 1];
+ byte expectedChecksum = calcChecksum(message, true);
+
+ if (messageChecksum != expectedChecksum) {
+ LOG.error("Invalid checksum for {}", hexdump);
+ return null;
+ }
+
+ int payloadLength = ((message[2] << 24) & 0xFF000000) | ((message[3] << 16) & 0xFF0000) | ((message[4] << 8) & 0xFF00) | ((message[5] << 24) & 0xFF000000);
+ if (payloadLength != message.length - 7) {
+ LOG.error("Unexpected payload length {}, expected {}", message.length - 6, payloadLength);
+ return null;
+ }
+
+ sequenceNumber = message[1];
+
+ if (message[0] == MSG_TYPE_ACK) {
+ LOG.info("Received ACK: {}", hexdump);
+ return new GBDeviceEvent[]{};
+ }
+
+ LOG.warn("Unknown message: {}", hexdump);
+
+ return null;
+ }
+
+ @Override
+ public byte[] encodeSendConfiguration(String config) {
+ final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
+ EqualizerPreset equalizerPreset = EqualizerPreset.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE, "off").toUpperCase());
+
+ switch (config) {
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_CONTROL:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_FOCUS_VOICE:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_LEVEL:
+ String soundControl = prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_CONTROL, "noise_cancelling");
+ boolean focusVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_FOCUS_VOICE, false);
+ int level = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AMBIENT_SOUND_LEVEL, 0);
+ return encodeSoundControl(AmbientSoundControl.valueOf(soundControl.toUpperCase()), focusVoice, level);
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SOUND_POSITION:
+ return encodeSoundPosition(
+ SoundPosition.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SOUND_POSITION, "off").toUpperCase())
+ );
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SURROUND_MODE:
+ return encodeSurroundMode(
+ SurroundMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_SURROUND_MODE, "off").toUpperCase())
+ );
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE:
+ return encodeEqualizerPreset(equalizerPreset);
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_400:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_1000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_2500:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_6300:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_16000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_CLEAR_BASS:
+ int m_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_400, 10) - 10;
+ int m_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_1000, 10) - 10;
+ int m_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_2500, 10) - 10;
+ int m_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_6300, 10) - 10;
+ int m_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_BAND_16000, 10) - 10;
+ int m_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MANUAL_CLEAR_BASS, 10) - 10;
+
+ // Set the equalizer preset, since changing the bands will switch it
+ // TODO: This is not updating the UI once the user returns to the previous screen
+ prefs.edit().putString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE, EqualizerPreset.MANUAL.toString().toLowerCase()).apply();
+
+ return encodeEqualizerCustomBands(EqualizerPreset.MANUAL, equalizerPreset, new EqualizerCustomBands(Arrays.asList(m_band1, m_band2, m_band3, m_band4, m_band5), m_bass));
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_400:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_1000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_2500:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_6300:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_16000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_CLEAR_BASS:
+ int c1_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_400, 10) - 10;
+ int c1_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_1000, 10) - 10;
+ int c1_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_2500, 10) - 10;
+ int c1_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_6300, 10) - 10;
+ int c1_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_BAND_16000, 10) - 10;
+ int c1_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_1_CLEAR_BASS, 10) - 10;
+
+ // Set the equalizer preset, since changing the bands will switch it
+ // TODO: This is not updating the UI once the user returns to the previous screen
+ prefs.edit().putString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE, EqualizerPreset.CUSTOM_1.toString().toLowerCase()).apply();
+
+ return encodeEqualizerCustomBands(EqualizerPreset.CUSTOM_1, equalizerPreset, new EqualizerCustomBands(Arrays.asList(c1_band1, c1_band2, c1_band3, c1_band4, c1_band5), c1_bass));
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_400:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_1000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_2500:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_6300:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_16000:
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_CLEAR_BASS:
+ int c2_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_400, 10) - 10;
+ int c2_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_1000, 10) - 10;
+ int c2_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_2500, 10) - 10;
+ int c2_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_6300, 10) - 10;
+ int c2_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_BAND_16000, 10) - 10;
+ int c2_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_CUSTOM_2_CLEAR_BASS, 10) - 10;
+
+ // Set the equalizer preset, since changing the bands will switch it
+ // TODO: This is not updating the UI once the user returns to the previous screen
+ prefs.edit().putString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_EQUALIZER_MODE, EqualizerPreset.CUSTOM_2.toString().toLowerCase()).apply();
+
+ return encodeEqualizerCustomBands(EqualizerPreset.CUSTOM_2, equalizerPreset, new EqualizerCustomBands(Arrays.asList(c2_band1, c2_band2, c2_band3, c2_band4, c2_band5), c2_bass));
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_DSEE_HX:
+ return encodeDSEEHX(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_DSEE_HX, false));
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_TOUCH_SENSOR:
+ return encodeTouchSensor(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_TOUCH_SENSOR, true));
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AUTOMATIC_POWER_OFF:
+ return encodeAutomaticPowerOff(
+ AutomaticPowerOff.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_AUTOMATIC_POWER_OFF, "off").toUpperCase())
+ );
+
+ case DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_NOTIFICATION_VOICE_GUIDE:
+ return encodeVoiceNotifications(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_WH1000XM3_NOTIFICATION_VOICE_GUIDE, true));
+
+ default:
+ LOG.warn("Unknown config '{}'", config);
+ }
+
+ return super.encodeSendConfiguration(config);
+ }
+
+ public byte[] encodeTriggerNoiseCancellingOptimizer() {
+ // This successfully triggers the noise cancelling optimizer. However, we don't get the
+ // optimization progress messages.
+
+ ByteBuffer buf = ByteBuffer.allocate(10);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(4);
+
+ buf.put((byte) 0x84);
+ buf.put((byte) 0x01);
+ buf.put((byte) 0x00);
+ buf.put((byte) 0x01);
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeSoundControl(AmbientSoundControl ambientSoundControl, boolean focusOnVoice, int ambientSound) {
+ if (ambientSound < 0 || ambientSound > 19) {
+ throw new IllegalArgumentException("Level must be between 0 and 19");
+ }
+
+ ByteBuffer buf = ByteBuffer.allocate(14);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(8);
+
+ buf.put((byte) 0x68);
+ buf.put((byte) 0x02);
+ if (AmbientSoundControl.OFF.equals(ambientSoundControl)) {
+ buf.put((byte) 0x00);
+ } else {
+ buf.put((byte) 0x11);
+ }
+ buf.put((byte) 0x01);
+
+ switch (ambientSoundControl) {
+ case NOISE_CANCELLING:
+ buf.put((byte) 2);
+ break;
+ case WIND_NOISE_REDUCTION:
+ buf.put((byte) 1);
+ break;
+ case OFF:
+ case AMBIENT_SOUND:
+ default:
+ buf.put((byte) 0);
+ break;
+ }
+
+ buf.put((byte) 0x01);
+ buf.put((byte) (focusOnVoice ? 0x01 : 0x00));
+ buf.put((byte) (ambientSound + 1));
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeSoundPosition(SoundPosition position) {
+ final ByteBuffer buf = ByteBuffer.allocate(9);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(3);
+
+ buf.put((byte) 0x48);
+ buf.put(CMD_SOUND_POSITION);
+
+ switch (position) {
+ case OFF:
+ buf.put((byte) 0x00);
+ break;
+ case FRONT:
+ buf.put((byte) 0x03);
+ break;
+ case FRONT_LEFT:
+ buf.put((byte) 0x01);
+ break;
+ case FRONT_RIGHT:
+ buf.put((byte) 0x02);
+ break;
+ case REAR_LEFT:
+ buf.put((byte) 0x11);
+ break;
+ case REAR_RIGHT:
+ buf.put((byte) 0x12);
+ break;
+ }
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeSurroundMode(SurroundMode mode) {
+ final ByteBuffer buf = ByteBuffer.allocate(9);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(3);
+
+ buf.put((byte) 0x48);
+ buf.put(CMD_SOUND_SURROUND);
+
+ switch (mode) {
+ case OFF:
+ buf.put((byte) 0x00);
+ break;
+ case ARENA:
+ buf.put((byte) 0x02);
+ break;
+ case CLUB:
+ buf.put((byte) 0x04);
+ break;
+ case OUTDOOR_STAGE:
+ buf.put((byte) 0x01);
+ break;
+ case CONCERT_HALL:
+ buf.put((byte) 0x03);
+ break;
+ }
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeEqualizerPreset(EqualizerPreset preset) {
+ final ByteBuffer buf = ByteBuffer.allocate(10);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(4);
+
+ buf.put((byte) 0x58);
+ buf.put((byte) 0x01);
+
+ switch (preset) {
+ case OFF:
+ buf.put((byte) 0x00).put((byte) 0x00);
+ break;
+ case BRIGHT:
+ buf.put((byte) 0x10).put((byte) 0x00);
+ break;
+ case EXCITED:
+ buf.put((byte) 0x11).put((byte) 0x00);
+ break;
+ case MELLOW:
+ buf.put((byte) 0x12).put((byte) 0x00);
+ break;
+ case RELAXED:
+ buf.put((byte) 0x13).put((byte) 0x00);
+ break;
+ case VOCAL:
+ buf.put((byte) 0x14).put((byte) 0x00);
+ break;
+ case TREBLE_BOOST:
+ buf.put((byte) 0x15).put((byte) 0x00);
+ break;
+ case BASS_BOOST:
+ buf.put((byte) 0x16).put((byte) 0x00);
+ break;
+ case SPEECH:
+ buf.put((byte) 0x17).put((byte) 0x00);
+ break;
+ case MANUAL:
+ buf.put((byte) 0xa0).put((byte) 0x00);
+ break;
+ case CUSTOM_1:
+ buf.put((byte) 0xa1).put((byte) 0x00);
+ break;
+ case CUSTOM_2:
+ buf.put((byte) 0xa2).put((byte) 0x00);
+ break;
+ }
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeEqualizerCustomBands(EqualizerPreset preset, EqualizerPreset previousPreset, EqualizerCustomBands equalizer) {
+ ByteArrayOutputStream cmdStream = new ByteArrayOutputStream(16);
+
+ try {
+ if (preset != previousPreset) {
+ // If we're not on the preset that is being changed, we need to swap to it
+ cmdStream.write(encodeEqualizerPreset(preset));
+ }
+
+ cmdStream.write(encodeEqualizerCustomBands(equalizer));
+
+ if (preset != previousPreset) {
+ // And then we swap back to the previous preset
+ // FIXME: this is not working, the new preset stays
+ //cmdStream.write(encodeEqualizerPreset(previousPreset));
+ }
+ } catch (IOException e) {
+ LOG.error("This should never happen", e);
+ }
+
+ return cmdStream.toByteArray();
+ }
+
+ private byte[] encodeEqualizerCustomBands(EqualizerCustomBands equalizer) {
+ final ByteBuffer buf = ByteBuffer.allocate(16);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(10);
+
+ buf.put((byte) 0x58);
+ buf.put((byte) 0x01);
+ buf.put((byte) 0xff);
+ buf.put((byte) 0x06);
+
+ buf.put((byte) (equalizer.getClearBass() + 10));
+ for (final Integer band : equalizer.getBands()) {
+ buf.put((byte) (band + 10));
+ }
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeDSEEHX(boolean enabled) {
+ ByteBuffer buf = ByteBuffer.allocate(10);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(4);
+
+ buf.put((byte) 0xe8);
+ buf.put((byte) 0x02);
+ buf.put((byte) 0x00);
+ buf.put((byte) (enabled ? 0x01 : 0x00));
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeTouchSensor(boolean enabled) {
+ ByteBuffer buf = ByteBuffer.allocate(10);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(4);
+
+ buf.put((byte) 0xd8);
+ buf.put((byte) 0xd2);
+ buf.put((byte) 0x01);
+ buf.put((byte) (enabled ? 0x01 : 0x00));
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeAutomaticPowerOff(AutomaticPowerOff automaticPowerOff) {
+ ByteBuffer buf = ByteBuffer.allocate(11);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0c);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(5);
+
+ buf.put((byte) 0xf8);
+ buf.put((byte) 0x04);
+ buf.put((byte) 0x01);
+
+ switch (automaticPowerOff) {
+ case OFF:
+ buf.put((byte) 0x11).put((byte) 0x00);
+ break;
+ case AFTER_5_MIN:
+ buf.put((byte) 0x00).put((byte) 0x00);
+ break;
+ case AFTER_30_MIN:
+ buf.put((byte) 0x01).put((byte) 0x01);
+ break;
+ case AFTER_1_HOUR:
+ buf.put((byte) 0x02).put((byte) 0x02);
+ break;
+ case AFTER_3_HOUR:
+ buf.put((byte) 0x03).put((byte) 0x03);
+ break;
+ }
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeVoiceNotifications(boolean enabled) {
+ ByteBuffer buf = ByteBuffer.allocate(10);
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ buf.put((byte) 0x0e);
+ buf.put((byte) sequenceNumber++);
+ buf.putInt(4);
+
+ buf.put((byte) 0x48);
+ buf.put((byte) 0x01);
+ buf.put((byte) 0x01);
+ buf.put((byte) (enabled ? 0x01 : 0x00));
+
+ return encodeMessage(buf.array());
+ }
+
+ private byte[] encodeMessage(byte[] message) {
+ ByteArrayOutputStream cmdStream = new ByteArrayOutputStream(message.length + 2);
+
+ cmdStream.write(PACKET_HEADER);
+
+ byte checksum = calcChecksum(message, false);
+
+ try {
+ cmdStream.write(escape(message));
+ cmdStream.write(escape(new byte[]{checksum}));
+ } catch (IOException e) {
+ LOG.error("This should never happen", e);
+ }
+
+ cmdStream.write(PACKET_TRAILER);
+
+ return cmdStream.toByteArray();
+ }
+
+ private byte[] escape(byte[] bytes) {
+ ByteArrayOutputStream escapedStream = new ByteArrayOutputStream(bytes.length);
+
+ for (byte b : bytes) {
+ switch (b) {
+ case PACKET_HEADER:
+ case PACKET_TRAILER:
+ case PACKET_ESCAPE:
+ escapedStream.write(PACKET_ESCAPE);
+ escapedStream.write(b & PACKET_ESCAPE_MASK);
+ break;
+ default:
+ escapedStream.write(b);
+ break;
+ }
+ }
+
+ return escapedStream.toByteArray();
+ }
+
+ private byte[] unescape(byte[] bytes) {
+ ByteArrayOutputStream unescapedStream = new ByteArrayOutputStream(bytes.length);
+
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ if (b == PACKET_ESCAPE) {
+ if (++i >= bytes.length) {
+ throw new IllegalArgumentException("Invalid escape character at end of array");
+ }
+ unescapedStream.write(b & ~PACKET_ESCAPE_MASK);
+ } else {
+ unescapedStream.write(b);
+ }
+ }
+
+ return unescapedStream.toByteArray();
+ }
+
+ public byte calcChecksum(byte[] message, boolean ignoreLastByte) {
+ int chk = 0;
+ for (int i = 0; i < message.length - (ignoreLastByte ? 1 : 0); i++) {
+ chk += message[i] & 255;
+ }
+ return (byte) chk;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Support.java
new file mode 100644
index 000000000..ff9b7decc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wh1000xm3/SonyWh1000Xm3Support.java
@@ -0,0 +1,96 @@
+/* Copyright (C) 2021 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wh1000xm3;
+
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class SonyWh1000Xm3Support extends AbstractSerialDeviceSupport {
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWh1000Xm3Support.class);
+
+ @Override
+ public boolean connect() {
+ getDeviceIOThread().start();
+
+ return true;
+ }
+
+ @Override
+ protected GBDeviceProtocol createDeviceProtocol() {
+ return new SonyWh1000Xm3Protocol(getDevice());
+ }
+
+ @Override
+ protected GBDeviceIoThread createDeviceIOThread() {
+ return new SonyWh1000Xm3IoThread(getDevice(), getContext(), (SonyWh1000Xm3Protocol) getDeviceProtocol(), SonyWh1000Xm3Support.this, getBluetoothAdapter());
+ }
+
+ @Override
+ public synchronized SonyWh1000Xm3IoThread getDeviceIOThread() {
+ return (SonyWh1000Xm3IoThread) super.getDeviceIOThread();
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return false;
+ }
+
+ @Override
+ public void onInstallApp(Uri uri) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onAppConfiguration(UUID uuid, String config, Integer id) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onHeartRateTest() {
+ // Nothing to do
+ }
+
+ @Override
+ public void onSetConstantVibration(int intensity) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onSetHeartRateMeasurementInterval(int seconds) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onReadConfiguration(String config) {
+ // Nothing to do
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
index b15d80c07..c7da8fc78 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -112,6 +112,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator.UM25Coordin
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wh1000xm3.SonyWh1000Xm3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
@@ -311,6 +312,8 @@ public class DeviceHelper {
result.add(new FitProDeviceCoordinator());
result.add(new Ear1Coordinator());
result.add(new GalaxyBudsDeviceCoordinator());
+ result.add(new SonyWh1000Xm3Coordinator());
+
return result;
}
diff --git a/app/src/main/res/drawable/ic_device_headphones.xml b/app/src/main/res/drawable/ic_device_headphones.xml
new file mode 100644
index 000000000..f56513664
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_headphones.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_device_headphones_disabled.xml b/app/src/main/res/drawable/ic_device_headphones_disabled.xml
new file mode 100644
index 000000000..15026d7d2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_headphones_disabled.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_speaker.xml b/app/src/main/res/drawable/ic_speaker.xml
new file mode 100644
index 000000000..c5fd13d52
--- /dev/null
+++ b/app/src/main/res/drawable/ic_speaker.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_surround.xml b/app/src/main/res/drawable/ic_surround.xml
new file mode 100644
index 000000000..7b89bbae5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_surround.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 7ccb7db4c..0f2c39aae 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -1166,4 +1166,58 @@
\nNota: não precisa instalar .res e .gps se esses forem exatamente os mesmos que os instalados anteriormente.
\n
\nPROSSIGA AO SEU PRÓPRIO RISCO!
+ Controlo Som Ambiente
+ Modo
+ Desligado
+ Cancelamento de ruído
+ Redução de ruído do vento
+ Som ambiente
+ Foco na voz
+ Nível de som ambiente
+ Posição do som
+ Desligado
+ Frente
+ Frente Esquerda
+ Frente Direita
+ Traseira Esquerda
+ Traseira Direita
+ Modo Surround
+ Desligado
+ Arena
+ Discoteca
+ Palco exterior
+ Sala de concertos
+ Aviso: As preferencias de posição de som / modo surround são mutuamente exclusivas.
+ Aviso: Estas preferencias só funcionam com o codec de audio SBC.
+ Equalizador
+ Desligado
+ Brilhante
+ Excitado
+ Suave
+ Relaxado
+ Vocal
+ Amplificação de agudos
+ Amplificação de graves
+ Discurso
+ Manual
+ Pessoal 1
+ Pessoal 2
+ Predefinição Manual
+ Predefinição Pessoal 1
+ Predefinição Pessoal 2
+ 400
+ 1k
+ 2.5k
+ 6.3k
+ 16k
+ Clear Bass
+ DSEE HX
+ Controlo com sensor tátil
+ Notificações & Guia de voz
+ Desligar automaticamente
+ Não desligar
+ 5 minutos
+ 30 minutos
+ 1 hora
+ 3 horas
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 52a92358b..ac55e397d 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1833,5 +1833,98 @@
- 3
+
+ - @string/sony_ambient_sound_off
+ - @string/sony_ambient_sound_noise_cancelling
+ - @string/sony_ambient_sound_wind_noise_reduction
+ - @string/sony_ambient_sound_ambient_sound
+
+
+
+ - off
+ - noise_cancelling
+ - wind_noise_reduction
+ - ambient_sound
+
+
+
+ - @string/sony_sound_position_off
+ - @string/sony_sound_position_front
+ - @string/sony_sound_position_front_left
+ - @string/sony_sound_position_front_right
+ - @string/sony_sound_position_rear_left
+ - @string/sony_sound_position_rear_right
+
+
+
+ - off
+ - front
+ - front_left
+ - front_right
+ - rear_left
+ - rear_right
+
+
+
+ - @string/sony_surround_mode_off
+ - @string/sony_surround_mode_arena
+ - @string/sony_surround_mode_club
+ - @string/sony_surround_mode_outdoor_stage
+ - @string/sony_surround_mode_concert_hall
+
+
+
+ - off
+ - arena
+ - club
+ - outdoor_stage
+ - concert_hall
+
+
+
+ - @string/sony_equalizer_preset_off
+ - @string/sony_equalizer_preset_bright
+ - @string/sony_equalizer_preset_excited
+ - @string/sony_equalizer_preset_mellow
+ - @string/sony_equalizer_preset_relaxed
+ - @string/sony_equalizer_preset_vocal
+ - @string/sony_equalizer_preset_treble_boost
+ - @string/sony_equalizer_preset_bass_boost
+ - @string/sony_equalizer_preset_speech
+ - @string/sony_equalizer_preset_manual
+ - @string/sony_equalizer_preset_custom_1
+ - @string/sony_equalizer_preset_custom_2
+
+
+
+ - off
+ - bright
+ - excited
+ - mellow
+ - relaxed
+ - vocal
+ - treble_boost
+ - bass_boost
+ - speech
+ - manual
+ - custom_1
+ - custom_2
+
+
+
+ - @string/sony_automatic_power_off_off
+ - @string/sony_automatic_power_off_5_min
+ - @string/sony_automatic_power_off_30_min
+ - @string/sony_automatic_power_off_1_hour
+ - @string/sony_automatic_power_off_3_hour
+
+
+
+ - off
+ - after_5_min
+ - after_30_min
+ - after_1_hour
+ - after_3_hour
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index edd0fdcf1..8b6222fa3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -101,6 +101,9 @@
Settings
General settings
+ Other
+ System
+ Equalizer
Connect to Gadgetbridge device when Bluetooth is turned on
Start automatically
Reconnect automatically
@@ -888,6 +891,7 @@
SMA-Q2 OSS
FitPro
+ Sony WH-1000XM3
Choose export location
General
High-priority
@@ -1312,4 +1316,59 @@
Battery case
Left earbud
Right earbud
+ Ambient Sound Control
+ Mode
+ Off
+ Noise Cancelling
+ Wind Noise Reduction
+ Ambient Sound
+ Focus on Voice
+ Ambient Sound Level
+ Sound Position
+ Off
+ Front
+ Front Left
+ Front Right
+ Rear Left
+ Rear Right
+ Surround Mode
+ Off
+ Arena
+ Club
+ Outdoor Stage
+ Concert Hall
+ Warning: The sound position / surround mode settings are mutually exclusive.
+ Warning: These settings only work for the SBC audio codec.
+ Equalizer
+ Off
+ Bright
+ Excited
+ Mellow
+ Relaxed
+ Vocal
+ Treble Boost
+ Bass Boost
+ Speech
+ Manual
+ Custom 1
+ Custom 2
+ Manual Preset
+ Custom Preset 1
+ Custom Preset 2
+ 400
+ 1k
+ 2.5k
+ 6.3k
+ 16k
+ Clear Bass
+ DSEE HX
+ Touch sensor control
+ Notifications & Voice Guide
+ Automatic Power Off
+ Do not turn off
+ 5 minutes
+ 30 minutes
+ 1 hour
+ 3 hours
+
diff --git a/app/src/main/res/xml/devicesettings_sony_1000xm3.xml b/app/src/main/res/xml/devicesettings_sony_1000xm3.xml
new file mode 100644
index 000000000..f2f0fb161
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sony_1000xm3.xml
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+