mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-29 05:16:51 +01:00
Sony WH-1000XM3: Read information from device
This commit is contained in:
parent
13e7c94e09
commit
33b5a6de38
@ -112,34 +112,20 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_GALAXY_BUDS_LIVE_ANC = "pref_galaxy_buds_live_anc";
|
||||
public static final String PREF_GALAXY_BUDS_PRESSURE_RELIEF = "pref_galaxy_buds_live_pressure_relief";
|
||||
|
||||
public static final String PREF_SONY_AUDIO_CODEC = "pref_sony_audio_codec";
|
||||
public static final String PREF_SONY_AMBIENT_SOUND_CONTROL = "pref_sony_ambient_sound_control";
|
||||
public static final String PREF_SONY_FOCUS_VOICE = "pref_sony_focus_voice";
|
||||
public static final String PREF_SONY_AMBIENT_SOUND_LEVEL = "pref_sony_ambient_sound_level";
|
||||
public static final String PREF_SONY_SOUND_POSITION = "pref_sony_sound_position";
|
||||
public static final String PREF_SONY_SURROUND_MODE = "pref_sony_surround_mode";
|
||||
public static final String PREF_SONY_EQUALIZER_MODE = "pref_sony_equalizer_mode";
|
||||
public static final String PREF_SONY_DSEE_HX = "pref_sony_dsee_hx";
|
||||
public static final String PREF_SONY_EQUALIZER_PRESET_MANUAL = "pref_sony_equalizer_preset_manual";
|
||||
public static final String PREF_SONY_EQUALIZER_PRESET_CUSTOM_1 = "pref_sony_equalizer_preset_custom_1";
|
||||
public static final String PREF_SONY_EQUALIZER_PRESET_CUSTOM_2 = "pref_sony_equalizer_preset_custom_2";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_BAND_400 = "pref_sony_equalizer_manual_band_400";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_BAND_1000 = "pref_sony_equalizer_manual_band_1000";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_BAND_2500 = "pref_sony_equalizer_manual_band_2500";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_BAND_6300 = "pref_sony_equalizer_manual_band_6300";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_BAND_16000 = "pref_sony_equalizer_manual_band_16000";
|
||||
public static final String PREF_SONY_EQUALIZER_MANUAL_CLEAR_BASS = "pref_sony_equalizer_manual_clear_bass";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_BAND_400 = "pref_sony_equalizer_custom_1_band_400";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_BAND_1000 = "pref_sony_equalizer_custom_1_band_1000";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_BAND_2500 = "pref_sony_equalizer_custom_1_band_2500";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_BAND_6300 = "pref_sony_equalizer_custom_1_band_6300";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_BAND_16000 = "pref_sony_equalizer_custom_1_band_16000";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_1_CLEAR_BASS = "pref_sony_equalizer_custom_1_clear_bass";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_BAND_400 = "pref_sony_equalizer_custom_2_band_400";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_BAND_1000 = "pref_sony_equalizer_custom_2_band_1000";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_BAND_2500 = "pref_sony_equalizer_custom_2_band_2500";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_BAND_6300 = "pref_sony_equalizer_custom_2_band_6300";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_BAND_16000 = "pref_sony_equalizer_custom_2_band_16000";
|
||||
public static final String PREF_SONY_EQUALIZER_CUSTOM_2_CLEAR_BASS = "pref_sony_equalizer_custom_2_clear_bass";
|
||||
public static final String PREF_SONY_AUDIO_UPSAMPLING = "pref_sony_audio_upsampling";
|
||||
public static final String PREF_SONY_EQUALIZER_BAND_400 = "pref_sony_equalizer_band_400";
|
||||
public static final String PREF_SONY_EQUALIZER_BAND_1000 = "pref_sony_equalizer_band_1000";
|
||||
public static final String PREF_SONY_EQUALIZER_BAND_2500 = "pref_sony_equalizer_band_2500";
|
||||
public static final String PREF_SONY_EQUALIZER_BAND_6300 = "pref_sony_equalizer_band_6300";
|
||||
public static final String PREF_SONY_EQUALIZER_BAND_16000 = "pref_sony_equalizer_band_16000";
|
||||
public static final String PREF_SONY_EQUALIZER_BASS = "pref_sony_equalizer_bass";
|
||||
public static final String PREF_SONY_TOUCH_SENSOR = "pref_sony_touch_sensor";
|
||||
public static final String PREF_SONY_AUTOMATIC_POWER_OFF = "pref_sony_automatic_power_off";
|
||||
public static final String PREF_SONY_NOTIFICATION_VOICE_GUIDE = "pref_sony_notification_voice_guide";
|
||||
|
@ -51,7 +51,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||
@ -124,7 +123,6 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SOUNDS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_ENABLED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_ENABLE;
|
||||
@ -136,25 +134,13 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_400;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_1000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_2500;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_6300;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_16000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_CLEAR_BASS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_400;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_1000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_2500;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_6300;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_16000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_CLEAR_BASS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_400;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_1000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_2500;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_6300;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_16000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_CLEAR_BASS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_DSEE_HX;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE;
|
||||
@ -566,25 +552,13 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
addPreferenceHandlerFor(PREF_SONY_SOUND_POSITION);
|
||||
addPreferenceHandlerFor(PREF_SONY_SURROUND_MODE);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MODE);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_BAND_400);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_BAND_1000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_BAND_2500);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_BAND_6300);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_BAND_16000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MANUAL_CLEAR_BASS);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_BAND_400);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_BAND_1000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_BAND_2500);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_BAND_6300);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_BAND_16000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_1_CLEAR_BASS);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_BAND_400);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_BAND_1000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_BAND_2500);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_BAND_6300);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_BAND_16000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_CUSTOM_2_CLEAR_BASS);
|
||||
addPreferenceHandlerFor(PREF_SONY_DSEE_HX);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_400);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_1000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_2500);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_6300);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BAND_16000);
|
||||
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BASS);
|
||||
addPreferenceHandlerFor(PREF_SONY_AUDIO_UPSAMPLING);
|
||||
addPreferenceHandlerFor(PREF_SONY_TOUCH_SENSOR);
|
||||
addPreferenceHandlerFor(PREF_SONY_AUTOMATIC_POWER_OFF);
|
||||
addPreferenceHandlerFor(PREF_SONY_NOTIFICATION_VOICE_GUIDE);
|
||||
|
@ -1,24 +0,0 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
|
||||
public enum AmbientSoundControl {
|
||||
OFF,
|
||||
NOISE_CANCELLING,
|
||||
WIND_NOISE_REDUCTION,
|
||||
AMBIENT_SOUND
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EqualizerCustomBands {
|
||||
private List<Integer> bands;
|
||||
private int clearBass;
|
||||
|
||||
public EqualizerCustomBands(List<Integer> 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<Integer> getBands() {
|
||||
return bands;
|
||||
}
|
||||
|
||||
public int getClearBass() {
|
||||
return clearBass;
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ public abstract class SonyHeadphonesCoordinator extends AbstractDeviceCoordinato
|
||||
|
||||
@Override
|
||||
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
|
||||
return new SonySettingsCustomizer();
|
||||
return new SonyHeadphonesSettingsCustomizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,56 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE;
|
||||
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
|
||||
public class SonyHeadphonesSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
|
||||
@Override
|
||||
public void customizeSettings(final DeviceSpecificSettingsHandler handler) {
|
||||
// Only enable the focus on voice check and voice level slider if the ambient sound control mode is ambient sound
|
||||
|
||||
final ListPreference ambientSoundControl = handler.findPreference(PREF_SONY_AMBIENT_SOUND_CONTROL);
|
||||
if (ambientSoundControl != null) {
|
||||
final Preference focusOnVoice = handler.findPreference(PREF_SONY_FOCUS_VOICE);
|
||||
final Preference ambientSoundLevel = handler.findPreference(PREF_SONY_AMBIENT_SOUND_LEVEL);
|
||||
|
||||
final Preference.OnPreferenceChangeListener ambientSoundControlPrefListener = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
boolean isAmbientSoundEnabled = AmbientSoundControl.Mode.AMBIENT_SOUND.name().toLowerCase(Locale.getDefault()).equals(newVal);
|
||||
focusOnVoice.setEnabled(isAmbientSoundEnabled);
|
||||
ambientSoundLevel.setEnabled(isAmbientSoundEnabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ambientSoundControlPrefListener.onPreferenceChange(ambientSoundControl, ambientSoundControl.getValue());
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL, ambientSoundControlPrefListener);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_PRESET_CUSTOM_1;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_PRESET_CUSTOM_2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_PRESET_MANUAL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE;
|
||||
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
|
||||
|
||||
public class SonySettingsCustomizer implements DeviceSpecificSettingsCustomizer {
|
||||
@Override
|
||||
public void customizeSettings(final DeviceSpecificSettingsHandler handler) {
|
||||
// Only enable the focus on voice check and voice level slider if the ambient sound control mode is ambient sound
|
||||
|
||||
final ListPreference ambientSoundControl = handler.findPreference(PREF_SONY_AMBIENT_SOUND_CONTROL);
|
||||
if (ambientSoundControl != null) {
|
||||
final Preference focusOnVoice = handler.findPreference(PREF_SONY_FOCUS_VOICE);
|
||||
final Preference ambientSoundLevel = handler.findPreference(PREF_SONY_AMBIENT_SOUND_LEVEL);
|
||||
|
||||
final Preference.OnPreferenceChangeListener ambientSoundControlPrefListener = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
boolean isAmbientSoundEnabled = AmbientSoundControl.AMBIENT_SOUND.name().toLowerCase(Locale.ROOT).equals(newVal);
|
||||
focusOnVoice.setEnabled(isAmbientSoundEnabled);
|
||||
ambientSoundLevel.setEnabled(isAmbientSoundEnabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ambientSoundControlPrefListener.onPreferenceChange(ambientSoundControl, ambientSoundControl.getValue());
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL, ambientSoundControlPrefListener);
|
||||
}
|
||||
|
||||
// Make the sound position and surround mode settings mutually exclusive
|
||||
|
||||
final ListPreference soundPositionPref = handler.findPreference(PREF_SONY_SOUND_POSITION);
|
||||
final ListPreference surroundModePref = handler.findPreference(PREF_SONY_SURROUND_MODE);
|
||||
|
||||
if (soundPositionPref != null && surroundModePref != null) {
|
||||
final Preference.OnPreferenceChangeListener soundPositionPrefListener = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
SoundPosition soundPosition = SoundPosition.valueOf(newVal.toString().toUpperCase(Locale.ROOT));
|
||||
surroundModePref.setEnabled(SoundPosition.OFF.equals(soundPosition));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
final Preference.OnPreferenceChangeListener surroundModePrefListener = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
SurroundMode surroundMode = SurroundMode.valueOf(newVal.toString().toUpperCase(Locale.ROOT));
|
||||
soundPositionPref.setEnabled(SurroundMode.OFF.equals(surroundMode));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
soundPositionPrefListener.onPreferenceChange(soundPositionPref, soundPositionPref.getValue());
|
||||
surroundModePrefListener.onPreferenceChange(surroundModePref, surroundModePref.getValue());
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_SOUND_POSITION, soundPositionPrefListener);
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_SURROUND_MODE, surroundModePrefListener);
|
||||
}
|
||||
|
||||
// Only enable the equalizer preset if the corresponding mode is selected
|
||||
|
||||
final ListPreference equalizerModePref = handler.findPreference(PREF_SONY_EQUALIZER_MODE);
|
||||
|
||||
if (equalizerModePref != null) {
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MODE);
|
||||
|
||||
final Preference presetManual = handler.findPreference(PREF_SONY_EQUALIZER_PRESET_MANUAL);
|
||||
final Preference presetCustom1 = handler.findPreference(PREF_SONY_EQUALIZER_PRESET_CUSTOM_1);
|
||||
final Preference presetCustom2 = handler.findPreference(PREF_SONY_EQUALIZER_PRESET_CUSTOM_2);
|
||||
|
||||
final Preference.OnPreferenceChangeListener equalizerModePrefListener = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final EqualizerPreset equalizerPreset = EqualizerPreset.valueOf(newVal.toString().toUpperCase(Locale.ROOT));
|
||||
|
||||
presetManual.setEnabled(EqualizerPreset.MANUAL.equals(equalizerPreset));
|
||||
presetCustom1.setEnabled(EqualizerPreset.CUSTOM_1.equals(equalizerPreset));
|
||||
presetCustom2.setEnabled(EqualizerPreset.CUSTOM_2.equals(equalizerPreset));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
equalizerModePrefListener.onPreferenceChange(equalizerModePref, equalizerModePref.getValue());
|
||||
handler.addPreferenceHandlerFor(PREF_SONY_EQUALIZER_MODE, equalizerModePrefListener);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.wh1000xm3;
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@ -24,13 +24,14 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class SonyWh1000Xm3Coordinator extends SonyHeadphonesCoordinator {
|
||||
public class SonyWH1000XM3Coordinator extends SonyHeadphonesCoordinator {
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
if (candidate.getName().contains("WH-1000XM3")) {
|
||||
return DeviceType.SONY_WH_1000XM3;
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@ -44,9 +45,15 @@ public class SonyWh1000Xm3Coordinator extends SonyHeadphonesCoordinator {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_sony_warning_wh1000xm3,
|
||||
R.xml.devicesettings_sony_headphones_ambient_sound_control,
|
||||
R.xml.devicesettings_header_other,
|
||||
R.xml.devicesettings_sony_headphones_equalizer,
|
||||
R.xml.devicesettings_sony_headphones_other,
|
||||
R.xml.devicesettings_sony_wh_1000xm3
|
||||
R.xml.devicesettings_sony_headphones_sound_position,
|
||||
R.xml.devicesettings_sony_headphones_surround_mode,
|
||||
R.xml.devicesettings_sony_headphones_audio_upsampling,
|
||||
R.xml.devicesettings_header_system,
|
||||
R.xml.devicesettings_sony_headphones_touch_sensor_single,
|
||||
R.xml.devicesettings_automatic_power_off,
|
||||
R.xml.devicesettings_sony_headphones_notifications_voice_guide
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public class AmbientSoundControl {
|
||||
public enum Mode {
|
||||
OFF,
|
||||
NOISE_CANCELLING,
|
||||
WIND_NOISE_REDUCTION,
|
||||
AMBIENT_SOUND
|
||||
}
|
||||
|
||||
private final Mode mode;
|
||||
private final boolean focusOnVoice;
|
||||
private final int ambientSound;
|
||||
|
||||
public AmbientSoundControl(Mode mode, boolean focusOnVoice, int ambientSound) {
|
||||
if (ambientSound < 0 || ambientSound > 20) {
|
||||
throw new IllegalArgumentException(String.format("Level must be between 0 and 20 (was %d)", ambientSound));
|
||||
}
|
||||
|
||||
this.mode = mode;
|
||||
this.focusOnVoice = focusOnVoice;
|
||||
this.ambientSound = ambientSound;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(Locale.getDefault(), "AmbientSoundControl{mode=%s, focusOnVoice=%s, ambientSound=%d}", mode, focusOnVoice, ambientSound);
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public boolean isFocusOnVoice() {
|
||||
return focusOnVoice;
|
||||
}
|
||||
|
||||
public int getAmbientSound() {
|
||||
return ambientSound;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL, mode.name().toLowerCase(Locale.getDefault()));
|
||||
|
||||
if (AmbientSoundControl.Mode.AMBIENT_SOUND.equals(mode)) {
|
||||
// Only use the ambient sound levels and focus on voice if we're on ambient sound mode,
|
||||
// to prevent overriding the user settings
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE, focusOnVoice);
|
||||
// Level is offset by 1 because we can't configure the SeekBarPreference min level on the current api level
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, ambientSound - 1);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
public static AmbientSoundControl fromPreferences(final SharedPreferences prefs) {
|
||||
final String soundControl = prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL, "noise_cancelling");
|
||||
final boolean focusVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE, false);
|
||||
// Level is offset by 1 because we can't configure the SeekBarPreference min level on the current api level
|
||||
final int ambientSound = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0) + 1;
|
||||
|
||||
return new AmbientSoundControl(AmbientSoundControl.Mode.valueOf(soundControl.toUpperCase()), focusVoice, ambientSound);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public class AudioUpsampling {
|
||||
private final boolean enabled;
|
||||
|
||||
public AudioUpsampling(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING, enabled);
|
||||
}};
|
||||
}
|
||||
|
||||
public static AudioUpsampling fromPreferences(final SharedPreferences prefs) {
|
||||
return new AudioUpsampling(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING, false));
|
||||
}
|
||||
}
|
@ -14,7 +14,15 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public enum AutomaticPowerOff {
|
||||
OFF(new byte[]{(byte) 0x11, (byte) 0x00}),
|
||||
@ -24,9 +32,23 @@ public enum AutomaticPowerOff {
|
||||
AFTER_3_HOUR(new byte[]{(byte) 0x03, (byte) 0x03}),
|
||||
WHEN_TAKEN_OFF(new byte[]{(byte) 0x10, (byte) 0x00});
|
||||
|
||||
public final byte[] code;
|
||||
private final byte[] code;
|
||||
|
||||
AutomaticPowerOff(final byte[] code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public byte[] getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, name().toLowerCase(Locale.getDefault()));
|
||||
}};
|
||||
}
|
||||
|
||||
public static AutomaticPowerOff fromPreferences(final SharedPreferences prefs) {
|
||||
return AutomaticPowerOff.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, "off").toUpperCase());
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public class EqualizerCustomBands {
|
||||
private List<Integer> bands;
|
||||
private int bass;
|
||||
|
||||
public EqualizerCustomBands(final List<Integer> bands, final int bass) {
|
||||
if (bands.size() != 5) {
|
||||
throw new IllegalArgumentException("Equalizer needs exactly 5 bands");
|
||||
}
|
||||
|
||||
for (final Integer band : bands) {
|
||||
if (band < -10 || band > 10) {
|
||||
throw new IllegalArgumentException(String.format("Bands should be between -10 and 10, got %d", band));
|
||||
}
|
||||
}
|
||||
|
||||
if (bass < -10 || bass > 10) {
|
||||
throw new IllegalArgumentException(String.format("Clear Bass value shoulud be between -10 and 10, got %d", bass));
|
||||
}
|
||||
|
||||
this.bands = bands;
|
||||
this.bass = bass;
|
||||
}
|
||||
|
||||
public List<Integer> getBands() {
|
||||
return bands;
|
||||
}
|
||||
|
||||
public int getBass() {
|
||||
return bass;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("EqualizerCustomBands{clearBass=%d, bands=%s}", bass, bands);
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400, bands.get(0) + 10);
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000, bands.get(1) + 10);
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500, bands.get(2) + 10);
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300, bands.get(3) + 10);
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000, bands.get(4) + 10);
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS, bass + 10);
|
||||
;
|
||||
}};
|
||||
}
|
||||
|
||||
public static EqualizerCustomBands fromPreferences(final SharedPreferences prefs) {
|
||||
int band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400, 10) - 10;
|
||||
int band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000, 10) - 10;
|
||||
int band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500, 10) - 10;
|
||||
int band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300, 10) - 10;
|
||||
int band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000, 10) - 10;
|
||||
int bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS, 10) - 10;
|
||||
|
||||
return new EqualizerCustomBands(Arrays.asList(band1, band2, band3, band4, band5), bass);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public enum EqualizerPreset {
|
||||
OFF((byte) 0x00),
|
||||
BRIGHT((byte) 0x10),
|
||||
EXCITED((byte) 0x11),
|
||||
MELLOW((byte) 0x12),
|
||||
RELAXED((byte) 0x13),
|
||||
VOCAL((byte) 0x14),
|
||||
TREBLE_BOOST((byte) 0x15),
|
||||
BASS_BOOST((byte) 0x16),
|
||||
SPEECH((byte) 0x17),
|
||||
MANUAL((byte) 0xa0),
|
||||
CUSTOM_1((byte) 0xa1),
|
||||
CUSTOM_2((byte) 0xa2);
|
||||
|
||||
private final byte code;
|
||||
|
||||
EqualizerPreset(final byte code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, name().toLowerCase(Locale.getDefault()));
|
||||
}};
|
||||
}
|
||||
|
||||
public static EqualizerPreset fromPreferences(final SharedPreferences prefs) {
|
||||
return EqualizerPreset.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, "off").toUpperCase());
|
||||
}
|
||||
}
|
@ -14,7 +14,15 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public enum SoundPosition {
|
||||
OFF((byte) 0x00),
|
||||
@ -24,9 +32,23 @@ public enum SoundPosition {
|
||||
REAR_LEFT((byte) 0x11),
|
||||
REAR_RIGHT((byte) 0x12);
|
||||
|
||||
public final byte code;
|
||||
private final byte code;
|
||||
|
||||
SoundPosition(final byte code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, name().toLowerCase(Locale.getDefault()));
|
||||
}};
|
||||
}
|
||||
|
||||
public static SoundPosition fromPreferences(final SharedPreferences prefs) {
|
||||
return SoundPosition.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, "off").toUpperCase());
|
||||
}
|
||||
}
|
@ -14,7 +14,15 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public enum SurroundMode {
|
||||
OFF((byte) 0x00),
|
||||
@ -23,9 +31,23 @@ public enum SurroundMode {
|
||||
OUTDOOR_STAGE((byte) 0x01),
|
||||
CONCERT_HALL((byte) 0x03);
|
||||
|
||||
public final byte code;
|
||||
private final byte code;
|
||||
|
||||
SurroundMode(final byte code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE, name().toLowerCase(Locale.getDefault()));
|
||||
}};
|
||||
}
|
||||
|
||||
public static SurroundMode fromPreferences(final SharedPreferences prefs) {
|
||||
return SurroundMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, "off").toUpperCase());
|
||||
}
|
||||
}
|
@ -14,25 +14,33 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
public enum EqualizerPreset {
|
||||
OFF(new byte[] {(byte) 0x00, (byte) 0x00}),
|
||||
BRIGHT(new byte[] {(byte) 0x10, (byte) 0x00}),
|
||||
EXCITED(new byte[] {(byte) 0x11, (byte) 0x00}),
|
||||
MELLOW(new byte[] {(byte) 0x12, (byte) 0x00}),
|
||||
RELAXED(new byte[] {(byte) 0x13, (byte) 0x00}),
|
||||
VOCAL(new byte[] {(byte) 0x14, (byte) 0x00}),
|
||||
TREBLE_BOOST(new byte[] {(byte) 0x15, (byte) 0x00}),
|
||||
BASS_BOOST(new byte[] {(byte) 0x16, (byte) 0x00}),
|
||||
SPEECH(new byte[] {(byte) 0x17, (byte) 0x00}),
|
||||
MANUAL(new byte[] {(byte) 0xa0, (byte) 0x00}),
|
||||
CUSTOM_1(new byte[] {(byte) 0xa1, (byte) 0x00}),
|
||||
CUSTOM_2(new byte[] {(byte) 0xa2, (byte) 0x00});
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public final byte[] code;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
EqualizerPreset(final byte[] code) {
|
||||
this.code = code;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public class TouchSensor {
|
||||
private final boolean enabled;
|
||||
|
||||
public TouchSensor(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR, enabled);
|
||||
}};
|
||||
}
|
||||
|
||||
public static TouchSensor fromPreferences(final SharedPreferences prefs) {
|
||||
return new TouchSensor(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR, true));
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
|
||||
public class VoiceNotifications {
|
||||
private final boolean enabled;
|
||||
|
||||
public VoiceNotifications(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, Object> toPreferences() {
|
||||
return new HashMap<String, Object>() {{
|
||||
put(DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE, enabled);
|
||||
}};
|
||||
}
|
||||
|
||||
public static VoiceNotifications fromPreferences(final SharedPreferences prefs) {
|
||||
return new VoiceNotifications(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE, true));
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@ public abstract class BtClassicIoThread extends GBDeviceIoThread {
|
||||
LOG.error("mOutStream is null");
|
||||
return;
|
||||
}
|
||||
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||
LOG.debug("writing: {}", GB.hexdump(bytes, 0, bytes.length));
|
||||
try {
|
||||
mOutStream.write(bytes);
|
||||
mOutStream.flush();
|
||||
|
@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -32,38 +33,80 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonyHeadphonesIoThread.class);
|
||||
|
||||
private final SonyHeadphonesProtocol mProtocol;
|
||||
|
||||
// Track whether we got the first reply
|
||||
private boolean firstReply = false;
|
||||
private final Handler handler = new Handler();
|
||||
private int initRetries = 0;
|
||||
|
||||
/**
|
||||
* Sometimes the headphones will ignore the first init request, so we retry a few times
|
||||
* TODO: Implement this in a more elegant way. Ideally, we should retry every command for which we didn't get an ACK.
|
||||
*/
|
||||
private final Runnable initSendRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// If we still haven't got any reply, re-send the init
|
||||
if (!firstReply) {
|
||||
if (initRetries++ < 2) {
|
||||
LOG.warn("Init retry {}", initRetries);
|
||||
|
||||
write(mProtocol.encodeInit());
|
||||
scheduleInitRetry();
|
||||
} else {
|
||||
LOG.error("Failed to start headphones init after {} tries", initRetries);
|
||||
quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SonyHeadphonesIoThread(GBDevice gbDevice, Context context, SonyHeadphonesProtocol protocol, SonyHeadphonesSupport support, BluetoothAdapter btAdapter) {
|
||||
super(gbDevice, context, protocol, support, btAdapter);
|
||||
mProtocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
write(mProtocol.encodeInit());
|
||||
scheduleInitRetry();
|
||||
setUpdateState(GBDevice.State.INITIALIZING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
// Log the human-readable message, for debugging
|
||||
LOG.info("Writing {}", Message.fromBytes(bytes));
|
||||
|
||||
super.write(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] parseIncoming(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||
byte[] incoming = new byte[1];
|
||||
final ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||
final byte[] incoming = new byte[1];
|
||||
|
||||
while (true) {
|
||||
do {
|
||||
inputStream.read(incoming);
|
||||
|
||||
if (incoming[0] == SonyHeadphonesProtocol.PACKET_HEADER) {
|
||||
if (incoming[0] == Message.MESSAGE_HEADER) {
|
||||
msgStream.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (incoming[0] == SonyHeadphonesProtocol.PACKET_TRAILER) {
|
||||
break;
|
||||
}
|
||||
|
||||
msgStream.write(incoming);
|
||||
}
|
||||
} while (incoming[0] != Message.MESSAGE_TRAILER);
|
||||
|
||||
byte[] msgArray = msgStream.toByteArray();
|
||||
LOG.debug("Received: " + GB.hexdump(msgArray, 0, msgArray.length));
|
||||
return msgArray;
|
||||
firstReply = true;
|
||||
|
||||
LOG.trace("Raw message: {}", GB.hexdump(msgStream.toByteArray()));
|
||||
|
||||
return msgStream.toByteArray();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -71,4 +114,10 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
|
||||
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
|
||||
return UUID.fromString("96CC203E-5068-46ad-B32D-E316F5E069BA");
|
||||
}
|
||||
|
||||
private void scheduleInitRetry() {
|
||||
LOG.info("Scheduling init retry");
|
||||
|
||||
handler.postDelayed(initSendRunnable, 1250);
|
||||
}
|
||||
}
|
||||
|
@ -21,386 +21,233 @@ 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 java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SoundPosition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SurroundMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public abstract class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonyHeadphonesProtocol.class);
|
||||
|
||||
/**
|
||||
* Packet format:
|
||||
* <p>
|
||||
* - PACKET_HEADER
|
||||
* - Command type? - almost always 0x0c or 0x0e?
|
||||
* - Sequence Number - needs to be updated with the one sent in the ACK 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
|
||||
* <p>
|
||||
* 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 byte sequenceNumber = 0;
|
||||
|
||||
// Request queue, sent every ACK, as we can't send them all at once
|
||||
// Initially, it should contain all the init requests
|
||||
private final Queue<Request> requestQueue = new LinkedList<>();
|
||||
private int pendingAcks = 0;
|
||||
|
||||
private AbstractSonyProtocolImpl protocolImpl = null;
|
||||
|
||||
public SonyHeadphonesProtocol(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);
|
||||
final Message message = Message.fromBytes(res);
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int payloadLength = ((message[2] << 24) & 0xFF000000) | ((message[3] << 16) & 0xFF0000) | ((message[4] << 8) & 0xFF00) | (message[5] & 0xFF);
|
||||
if (payloadLength != message.length - 7) {
|
||||
LOG.error("Unexpected payload length {}, expected {}", message.length - 7, payloadLength);
|
||||
LOG.info("Received {}", message);
|
||||
|
||||
final MessageType messageType = message.getType();
|
||||
|
||||
if (messageType == MessageType.ACK) {
|
||||
if (sequenceNumber == message.getSequenceNumber()) {
|
||||
LOG.warn("Unexpected ACK sequence number {}", message.getSequenceNumber());
|
||||
return null;
|
||||
}
|
||||
|
||||
sequenceNumber = message.getSequenceNumber();
|
||||
|
||||
return new GBDeviceEvent[]{handleAck()};
|
||||
}
|
||||
|
||||
if (message.getPayload().length == 0) {
|
||||
LOG.warn("Empty message: {}", message);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (message[0] == MSG_TYPE_ACK) {
|
||||
LOG.info("Received ACK: {}", hexdump);
|
||||
sequenceNumber = message[1];
|
||||
return new GBDeviceEvent[]{new GBDeviceEventSendBytes(encodeAck())};
|
||||
final List<Object> events = new ArrayList<>();
|
||||
|
||||
if (protocolImpl == null) {
|
||||
// Check if we got an init response, which should indicate the protocol version
|
||||
if (MessageType.COMMAND_1.equals(messageType) && message.getPayload()[0] == 0x01) {
|
||||
// Init reply, set the protocol version
|
||||
if (message.getPayload().length == 4) {
|
||||
protocolImpl = new SonyProtocolImplV1(getDevice());
|
||||
} else if (message.getPayload().length == 6) {
|
||||
LOG.warn("Sony Headphones protocol v2 is not yet supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warn("Unknown message: {}", hexdump);
|
||||
if (protocolImpl == null) {
|
||||
LOG.error("No protocol implementation, ignoring message");
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
try {
|
||||
switch (messageType) {
|
||||
case COMMAND_1:
|
||||
case COMMAND_2:
|
||||
events.add(new GBDeviceEventSendBytes(encodeAck(message.getSequenceNumber())));
|
||||
events.addAll(protocolImpl.handlePayload(messageType, message.getPayload()));
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown message type for {}", message);
|
||||
return null;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Don't crash the app if we somehow fail to handle the payload
|
||||
LOG.error("Error handling payload", e);
|
||||
}
|
||||
|
||||
return events.toArray(new GBDeviceEvent[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeSendConfiguration(String config) {
|
||||
if (protocolImpl == null) {
|
||||
LOG.error("No protocol implementation, ignoring config {}", config);
|
||||
return super.encodeSendConfiguration(config);
|
||||
}
|
||||
|
||||
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
||||
EqualizerPreset equalizerPreset = EqualizerPreset.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE, "off").toUpperCase());
|
||||
|
||||
final Request configRequest;
|
||||
|
||||
switch (config) {
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
|
||||
String soundControl = prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL, "noise_cancelling");
|
||||
boolean focusVoice = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE, false);
|
||||
int level = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0);
|
||||
return encodeSoundControl(AmbientSoundControl.valueOf(soundControl.toUpperCase()), focusVoice, level);
|
||||
|
||||
configRequest = protocolImpl.setAmbientSoundControl(AmbientSoundControl.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION:
|
||||
return encodeSoundPosition(
|
||||
SoundPosition.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION, "off").toUpperCase())
|
||||
);
|
||||
|
||||
configRequest = protocolImpl.setSoundPosition(SoundPosition.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE:
|
||||
return encodeSurroundMode(
|
||||
SurroundMode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE, "off").toUpperCase())
|
||||
);
|
||||
|
||||
configRequest = protocolImpl.setSurroundMode(SurroundMode.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE:
|
||||
return encodeEqualizerPreset(equalizerPreset);
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_400:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_1000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_2500:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_6300:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_16000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_CLEAR_BASS:
|
||||
int m_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_400, 10) - 10;
|
||||
int m_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_1000, 10) - 10;
|
||||
int m_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_2500, 10) - 10;
|
||||
int m_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_6300, 10) - 10;
|
||||
int m_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_BAND_16000, 10) - 10;
|
||||
int m_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MANUAL_CLEAR_BASS, 10) - 10;
|
||||
|
||||
return encodeEqualizerCustomBands(new EqualizerCustomBands(Arrays.asList(m_band1, m_band2, m_band3, m_band4, m_band5), m_bass));
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_400:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_1000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_2500:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_6300:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_16000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_CLEAR_BASS:
|
||||
int c1_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_400, 10) - 10;
|
||||
int c1_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_1000, 10) - 10;
|
||||
int c1_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_2500, 10) - 10;
|
||||
int c1_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_6300, 10) - 10;
|
||||
int c1_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_BAND_16000, 10) - 10;
|
||||
int c1_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_1_CLEAR_BASS, 10) - 10;
|
||||
|
||||
return encodeEqualizerCustomBands(new EqualizerCustomBands(Arrays.asList(c1_band1, c1_band2, c1_band3, c1_band4, c1_band5), c1_bass));
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_400:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_1000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_2500:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_6300:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_16000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_CLEAR_BASS:
|
||||
int c2_band1 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_400, 10) - 10;
|
||||
int c2_band2 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_1000, 10) - 10;
|
||||
int c2_band3 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_2500, 10) - 10;
|
||||
int c2_band4 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_6300, 10) - 10;
|
||||
int c2_band5 = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_BAND_16000, 10) - 10;
|
||||
int c2_bass = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_CUSTOM_2_CLEAR_BASS, 10) - 10;
|
||||
|
||||
return encodeEqualizerCustomBands(new EqualizerCustomBands(Arrays.asList(c2_band1, c2_band2, c2_band3, c2_band4, c2_band5), c2_bass));
|
||||
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_DSEE_HX:
|
||||
return encodeDSEEHX(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_DSEE_HX, false));
|
||||
|
||||
configRequest = protocolImpl.setEqualizerPreset(EqualizerPreset.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000:
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS:
|
||||
configRequest = protocolImpl.setEqualizerCustomBands(EqualizerCustomBands.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING:
|
||||
configRequest = protocolImpl.setAudioUpsampling(AudioUpsampling.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR:
|
||||
return encodeTouchSensor(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR, true));
|
||||
|
||||
configRequest = protocolImpl.setTouchSensor(TouchSensor.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF:
|
||||
return encodeAutomaticPowerOff(
|
||||
AutomaticPowerOff.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF, "off").toUpperCase())
|
||||
);
|
||||
|
||||
configRequest = protocolImpl.setAutomaticPowerOff(AutomaticPowerOff.fromPreferences(prefs));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE:
|
||||
return encodeVoiceNotifications(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE, true));
|
||||
configRequest = protocolImpl.setVoiceNotifications(VoiceNotifications.fromPreferences(prefs));
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG.warn("Unknown config '{}'", config);
|
||||
return super.encodeSendConfiguration(config);
|
||||
}
|
||||
|
||||
return super.encodeSendConfiguration(config);
|
||||
pendingAcks++;
|
||||
|
||||
return configRequest.encode(sequenceNumber);
|
||||
}
|
||||
|
||||
public byte[] encodeAck() {
|
||||
return encodeMessage(
|
||||
MSG_TYPE_ACK,
|
||||
new byte[]{}
|
||||
);
|
||||
}
|
||||
@Override
|
||||
public byte[] encodeTestNewFunction() {
|
||||
//return Request.fromHex(MessageType.COMMAND_1, "c40100").encode(sequenceNumber);
|
||||
|
||||
public byte[] encodeTriggerNoiseCancellingOptimizer() {
|
||||
// This successfully triggers the noise cancelling optimizer. However, we don't get the
|
||||
// optimization progress messages.
|
||||
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0x84, (byte) 0x01, (byte) 0x00, (byte) 0x01}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeSoundControl(AmbientSoundControl ambientSoundControl, boolean focusOnVoice, int ambientSound) {
|
||||
if (ambientSound < 0 || ambientSound > 19) {
|
||||
throw new IllegalArgumentException("Level must be between 0 and 19");
|
||||
if (protocolImpl != null) {
|
||||
return protocolImpl.startNoiseCancellingOptimizer().encode(sequenceNumber);
|
||||
}
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(14);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
buf.put((byte) 0x0c);
|
||||
buf.put(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());
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] encodeSoundPosition(SoundPosition position) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0x48, CMD_SOUND_POSITION, position.code}
|
||||
);
|
||||
public byte[] encodeAck(byte sequenceNumber) {
|
||||
return new Message(MessageType.ACK, (byte) (1 - sequenceNumber), new byte[0]).encode();
|
||||
}
|
||||
|
||||
private byte[] encodeSurroundMode(SurroundMode mode) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0x48, CMD_SOUND_SURROUND, mode.code}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeEqualizerPreset(EqualizerPreset preset) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0x58, (byte) 0x01, preset.code[0], preset.code[1]}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeEqualizerCustomBands(EqualizerCustomBands equalizer) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(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(
|
||||
(byte) 0x0c,
|
||||
buf.array()
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeDSEEHX(boolean enabled) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0xe8, (byte) 0x02, (byte) 0x00, (byte) (enabled ? 0x01 : 0x00)}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeTouchSensor(boolean enabled) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0xd8, (byte) 0xd2, (byte) 0x01, (byte) (enabled ? 0x01 : 0x00)}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeAutomaticPowerOff(AutomaticPowerOff automaticPowerOff) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0c,
|
||||
new byte[]{(byte) 0xf8, (byte) 0x04, (byte) 0x01, automaticPowerOff.code[0], automaticPowerOff.code[1]}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeVoiceNotifications(boolean enabled) {
|
||||
return encodeMessage(
|
||||
(byte) 0x0e,
|
||||
new byte[]{(byte) 0x48, (byte) 0x01, (byte) 0x01, (byte) (enabled ? 0x01 : 0x00)}
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] encodeMessage(byte type, byte[] content) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(content.length + 6);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
buf.put(type);
|
||||
buf.put(sequenceNumber);
|
||||
buf.putInt(content.length);
|
||||
buf.put(content);
|
||||
|
||||
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");
|
||||
public byte[] encodeInit() {
|
||||
pendingAcks++;
|
||||
return new Message(
|
||||
MessageType.COMMAND_1,
|
||||
sequenceNumber,
|
||||
new byte[]{
|
||||
(byte) 0x00,
|
||||
(byte) 0x00
|
||||
}
|
||||
unescapedStream.write(b & ~PACKET_ESCAPE_MASK);
|
||||
} else {
|
||||
unescapedStream.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
return unescapedStream.toByteArray();
|
||||
).encode();
|
||||
}
|
||||
|
||||
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;
|
||||
public void enqueueRequests(final List<Request> requests) {
|
||||
LOG.debug("Enqueueing {} requests", requests.size());
|
||||
|
||||
requestQueue.addAll(requests);
|
||||
}
|
||||
|
||||
public int getPendingAcks() {
|
||||
return pendingAcks;
|
||||
}
|
||||
|
||||
public byte[] getFromQueue() {
|
||||
return requestQueue.remove().encode(sequenceNumber);
|
||||
}
|
||||
|
||||
private GBDeviceEvent handleAck() {
|
||||
pendingAcks--;
|
||||
|
||||
if (!requestQueue.isEmpty()) {
|
||||
LOG.debug("Outstanding requests in queue: {}", requestQueue.size());
|
||||
|
||||
final Request request = requestQueue.remove();
|
||||
|
||||
return new GBDeviceEventSendBytes(request.encode(sequenceNumber));
|
||||
}
|
||||
return (byte) chk;
|
||||
|
||||
if (GBDevice.State.INITIALIZING.equals(getDevice().getState())) {
|
||||
// The queue is now empty, so we have got all the information from the device
|
||||
// Mark it as initialized
|
||||
|
||||
return new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.wh1000xm3.SonyWh1000Xm3Protocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents.SonyHeadphonesEnqueueRequestEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
@ -37,20 +37,12 @@ public class SonyHeadphonesSupport extends AbstractSerialDeviceSupport {
|
||||
@Override
|
||||
public boolean connect() {
|
||||
getDeviceIOThread().start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GBDeviceProtocol createDeviceProtocol() {
|
||||
DeviceType deviceType = getDevice().getType();
|
||||
switch (deviceType) {
|
||||
case SONY_WH_1000XM3:
|
||||
return new SonyWh1000Xm3Protocol(getDevice());
|
||||
default:
|
||||
LOG.error("Unsupported Sony device type '{}' with key '{}", deviceType, deviceType.getKey());
|
||||
return null;
|
||||
}
|
||||
return new SonyHeadphonesProtocol(getDevice());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,6 +55,25 @@ public class SonyHeadphonesSupport extends AbstractSerialDeviceSupport {
|
||||
return (SonyHeadphonesIoThread) super.getDeviceIOThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
|
||||
final SonyHeadphonesProtocol sonyProtocol = (SonyHeadphonesProtocol) getDeviceProtocol();
|
||||
|
||||
if (deviceEvent instanceof SonyHeadphonesEnqueueRequestEvent) {
|
||||
final SonyHeadphonesEnqueueRequestEvent enqueueRequestEvent = (SonyHeadphonesEnqueueRequestEvent) deviceEvent;
|
||||
sonyProtocol.enqueueRequests(enqueueRequestEvent.getRequests());
|
||||
|
||||
if (sonyProtocol.getPendingAcks() == 0) {
|
||||
// There are no pending acks, send one request from the queue
|
||||
// TODO: A more elegant way of scheduling these?
|
||||
SonyHeadphonesIoThread deviceIOThread = getDeviceIOThread();
|
||||
deviceIOThread.write(sonyProtocol.getFromQueue());
|
||||
}
|
||||
}
|
||||
|
||||
super.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
|
@ -0,0 +1,40 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
|
||||
public class SonyHeadphonesEnqueueRequestEvent extends GBDeviceEvent {
|
||||
private final List<Request> requests = new ArrayList<>();
|
||||
|
||||
public SonyHeadphonesEnqueueRequestEvent(final Request request) {
|
||||
this.requests.add(request);
|
||||
}
|
||||
|
||||
public SonyHeadphonesEnqueueRequestEvent(final Collection<Request> requests) {
|
||||
this.requests.addAll(requests);
|
||||
}
|
||||
|
||||
public List<Request> getRequests() {
|
||||
return this.requests;
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
|
||||
|
||||
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.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.PayloadType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class Message {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Message.class);
|
||||
|
||||
/**
|
||||
* Message format:
|
||||
* <p>
|
||||
* - MESSAGE_HEADER
|
||||
* - Message Type ({@link MessageType})
|
||||
* - Sequence Number - needs to be updated with the one sent in the ACK responses
|
||||
* - Payload Length - 4-byte big endian int with number of bytes that will follow
|
||||
* - N bytes of payload data (first being the {@link PayloadType})
|
||||
* - Checksum (1-byte sum, excluding header)
|
||||
* - MESSAGE_TRAILER
|
||||
* <p>
|
||||
* Data between MESSAGE_HEADER and MESSAGE_TRAILER is escaped with MESSAGE_ESCAPE, and the
|
||||
* following byte masked with MESSAGE_ESCAPE_MASK.
|
||||
*/
|
||||
|
||||
public static final byte MESSAGE_HEADER = 0x3e;
|
||||
public static final byte MESSAGE_TRAILER = 0x3c;
|
||||
public static final byte MESSAGE_ESCAPE = 0x3d;
|
||||
public static final byte MESSAGE_ESCAPE_MASK = (byte) 0b11101111;
|
||||
|
||||
private final MessageType type;
|
||||
private final byte sequenceNumber;
|
||||
private final byte[] payload;
|
||||
|
||||
public Message(final MessageType type, final byte sequenceNumber, final byte[] payload) {
|
||||
this.type = type;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public byte[] encode() {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(payload.length + 6);
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
buf.put(type.getCode());
|
||||
buf.put(sequenceNumber);
|
||||
buf.putInt(payload.length);
|
||||
buf.put(payload);
|
||||
|
||||
return encodeMessage(buf.array());
|
||||
}
|
||||
|
||||
public MessageType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public byte getSequenceNumber() {
|
||||
return this.sequenceNumber;
|
||||
}
|
||||
|
||||
public byte[] getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (payload.length > 0) {
|
||||
final PayloadType payloadType = PayloadType.fromCode(type, payload[0]);
|
||||
return String.format(Locale.getDefault(), "Message{Cmd=%s, Seq=%d, PayloadType=%s, Payload=%s}", type, sequenceNumber, payloadType, GB.hexdump(payload));
|
||||
} else {
|
||||
return String.format(Locale.getDefault(), "Message{Cmd=%s, Seq=%d}", type, sequenceNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public static Message fromBytes(final byte[] rawBytes) {
|
||||
if (rawBytes[0] != MESSAGE_HEADER) {
|
||||
throw new IllegalArgumentException(String.format("Invalid header %02x", rawBytes[0]));
|
||||
}
|
||||
|
||||
if (rawBytes[rawBytes.length - 1] != MESSAGE_TRAILER) {
|
||||
throw new IllegalArgumentException(String.format("Invalid trailer %02x", rawBytes[0]));
|
||||
}
|
||||
|
||||
final byte[] messageBytes = unescape(rawBytes);
|
||||
final String hexdump = GB.hexdump(messageBytes, 0, messageBytes.length);
|
||||
|
||||
final byte messageChecksum = messageBytes[messageBytes.length - 2];
|
||||
final byte expectedChecksum = calcChecksum(messageBytes, 1, messageBytes.length - 2);
|
||||
if (messageChecksum != expectedChecksum) {
|
||||
LOG.warn(String.format("Invalid checksum %02x for %s (expected %02x)", messageChecksum, hexdump, expectedChecksum));
|
||||
return null;
|
||||
}
|
||||
|
||||
final int payloadLength = ((messageBytes[3] << 24) & 0xFF000000) |
|
||||
((messageBytes[4] << 16) & 0xFF0000) |
|
||||
((messageBytes[5] << 8) & 0xFF00) |
|
||||
(messageBytes[6] & 0xFF);
|
||||
if (payloadLength != messageBytes.length - 9) {
|
||||
LOG.warn("Unexpected payload length {}, expected {}", messageBytes.length - 7, payloadLength);
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte rawMessageType = messageBytes[1];
|
||||
final MessageType messageType = MessageType.fromCode(rawMessageType);
|
||||
if (messageType == null) {
|
||||
LOG.warn("Unknown message type {}", String.format("%02x", rawMessageType));
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte sequenceNumber = messageBytes[2];
|
||||
final byte[] payload = new byte[payloadLength];
|
||||
System.arraycopy(messageBytes, 7, payload, 0, payloadLength);
|
||||
|
||||
return new Message(messageType, sequenceNumber, payload);
|
||||
}
|
||||
|
||||
public static byte[] encodeMessage(byte[] message) {
|
||||
final ByteArrayOutputStream cmdStream = new ByteArrayOutputStream(message.length + 2);
|
||||
|
||||
cmdStream.write(MESSAGE_HEADER);
|
||||
|
||||
final byte checksum = calcChecksum(message, 0, message.length);
|
||||
|
||||
try {
|
||||
cmdStream.write(escape(message));
|
||||
cmdStream.write(escape(new byte[]{checksum}));
|
||||
} catch (final IOException e) {
|
||||
LOG.error("This should never happen", e);
|
||||
}
|
||||
|
||||
cmdStream.write(MESSAGE_TRAILER);
|
||||
|
||||
return cmdStream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] escape(byte[] bytes) {
|
||||
final ByteArrayOutputStream escapedStream = new ByteArrayOutputStream(bytes.length);
|
||||
|
||||
for (byte b : bytes) {
|
||||
switch (b) {
|
||||
case MESSAGE_HEADER:
|
||||
case MESSAGE_TRAILER:
|
||||
case MESSAGE_ESCAPE:
|
||||
escapedStream.write(MESSAGE_ESCAPE);
|
||||
escapedStream.write(b & MESSAGE_ESCAPE_MASK);
|
||||
break;
|
||||
default:
|
||||
escapedStream.write(b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return escapedStream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] unescape(byte[] bytes) {
|
||||
final ByteArrayOutputStream unescapedStream = new ByteArrayOutputStream(bytes.length);
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
final byte b = bytes[i];
|
||||
if (b == MESSAGE_ESCAPE) {
|
||||
if (++i >= bytes.length) {
|
||||
throw new IllegalArgumentException("Invalid escape character at end of array");
|
||||
}
|
||||
unescapedStream.write(bytes[i] | ~MESSAGE_ESCAPE_MASK);
|
||||
} else {
|
||||
unescapedStream.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
return unescapedStream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte calcChecksum(byte[] message, int start, int end) {
|
||||
int chk = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
chk += message[i] & 255;
|
||||
}
|
||||
return (byte) chk;
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(final String payloadHex) {
|
||||
final String[] parts = payloadHex.split(":");
|
||||
|
||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
for (final String b : parts) {
|
||||
stream.write((byte) ((Character.digit(b.charAt(0), 16) << 4) + Character.digit(b.charAt(1), 16)));
|
||||
}
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
|
||||
|
||||
public enum MessageType {
|
||||
ACK(0x01),
|
||||
COMMAND_1(0x0c),
|
||||
COMMAND_2(0x0e),
|
||||
|
||||
UNKNOWN(0xff);
|
||||
|
||||
private final byte code;
|
||||
|
||||
MessageType(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static MessageType fromCode(final byte code) {
|
||||
for (final MessageType messageType : values()) {
|
||||
if (messageType.code == code) {
|
||||
return messageType;
|
||||
}
|
||||
}
|
||||
|
||||
return MessageType.UNKNOWN;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class Request {
|
||||
private final MessageType messageType;
|
||||
private final byte[] payload;
|
||||
|
||||
public Request(final MessageType messageType, final byte[] payload) {
|
||||
this.messageType = messageType;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public Request(final MessageType messageType, final int... payload) {
|
||||
this.messageType = messageType;
|
||||
this.payload = new byte[payload.length];
|
||||
|
||||
for (int i = 0; i < payload.length; i++) {
|
||||
this.payload[i] = (byte) payload[i];
|
||||
}
|
||||
}
|
||||
|
||||
public MessageType messageType() {
|
||||
return this.messageType;
|
||||
}
|
||||
|
||||
public byte[] payload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public final Message toMessage(final byte sequenceNumber) {
|
||||
return new Message(messageType(), sequenceNumber, payload());
|
||||
}
|
||||
|
||||
public final byte[] encode(final byte sequenceNumber) {
|
||||
return toMessage(sequenceNumber).encode();
|
||||
}
|
||||
|
||||
public static Request fromHex(final MessageType messageType, final String payload) {
|
||||
return new Request(messageType, GB.hexStringToByteArray(payload));
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
|
||||
public abstract class AbstractSonyProtocolImpl {
|
||||
private final GBDevice device;
|
||||
|
||||
public AbstractSonyProtocolImpl(final GBDevice device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
protected GBDevice getDevice() {
|
||||
return this.device;
|
||||
}
|
||||
|
||||
public abstract Request getAmbientSoundControl();
|
||||
|
||||
public abstract Request setAmbientSoundControl(final AmbientSoundControl config);
|
||||
|
||||
public abstract Request getAudioCodec();
|
||||
|
||||
public abstract Request getBattery(final BatteryType batteryType);
|
||||
|
||||
public abstract Request getFirmwareVersion();
|
||||
|
||||
public abstract Request getAudioUpsampling();
|
||||
|
||||
public abstract Request setAudioUpsampling(final AudioUpsampling config);
|
||||
|
||||
public abstract Request getAutomaticPowerOff();
|
||||
|
||||
public abstract Request setAutomaticPowerOff(final AutomaticPowerOff config);
|
||||
|
||||
public abstract Request getEqualizer();
|
||||
|
||||
public abstract Request setEqualizerPreset(final EqualizerPreset config);
|
||||
|
||||
public abstract Request setEqualizerCustomBands(final EqualizerCustomBands config);
|
||||
|
||||
public abstract Request getSoundPosition();
|
||||
|
||||
public abstract Request setSoundPosition(final SoundPosition config);
|
||||
|
||||
public abstract Request getSurroundMode();
|
||||
|
||||
public abstract Request setSurroundMode(final SurroundMode config);
|
||||
|
||||
public abstract Request getTouchSensor();
|
||||
|
||||
public abstract Request setTouchSensor(final TouchSensor config);
|
||||
|
||||
public abstract Request getVoiceNotifications();
|
||||
|
||||
public abstract Request setVoiceNotifications(final VoiceNotifications config);
|
||||
|
||||
public abstract Request startNoiseCancellingOptimizer();
|
||||
|
||||
public abstract List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload);
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
|
||||
public enum PayloadType {
|
||||
INIT_REQUEST(MessageType.COMMAND_1, 0x00),
|
||||
INIT_REPLY(MessageType.COMMAND_1, 0x01),
|
||||
|
||||
FW_VERSION_REQUEST(MessageType.COMMAND_1, 0x04),
|
||||
FW_VERSION_REPLY(MessageType.COMMAND_1, 0x05),
|
||||
|
||||
INIT_2_REQUEST(MessageType.COMMAND_1, 0x06),
|
||||
INIT_2_REPLY(MessageType.COMMAND_1, 0x07),
|
||||
|
||||
BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x10),
|
||||
BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x11),
|
||||
BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x13),
|
||||
|
||||
AUDIO_CODEC_REQUEST(MessageType.COMMAND_1, 0x18),
|
||||
AUDIO_CODEC_REPLY(MessageType.COMMAND_1, 0x19),
|
||||
AUDIO_CODEC_NOTIFY(MessageType.COMMAND_1, 0x1b),
|
||||
|
||||
SOUND_POSITION_OR_MODE_GET(MessageType.COMMAND_1, 0x46),
|
||||
SOUND_POSITION_OR_MODE_RET(MessageType.COMMAND_1, 0x47),
|
||||
SOUND_POSITION_OR_MODE_SET(MessageType.COMMAND_1, 0x48),
|
||||
SOUND_POSITION_OR_MODE_NOTIFY(MessageType.COMMAND_1, 0x49),
|
||||
|
||||
EQUALIZER_GET(MessageType.COMMAND_1, 0x56),
|
||||
EQUALIZER_RET(MessageType.COMMAND_1, 0x57),
|
||||
EQUALIZER_SET(MessageType.COMMAND_1, 0x58),
|
||||
EQUALIZER_NOTIFY(MessageType.COMMAND_1, 0x59),
|
||||
|
||||
AMBIENT_SOUND_CONTROL_GET(MessageType.COMMAND_1, 0x66),
|
||||
AMBIENT_SOUND_CONTROL_RET(MessageType.COMMAND_1, 0x67),
|
||||
AMBIENT_SOUND_CONTROL_SET(MessageType.COMMAND_1, 0x68),
|
||||
AMBIENT_SOUND_CONTROL_NOTIFY(MessageType.COMMAND_1, 0x69),
|
||||
|
||||
NOISE_CANCELLING_OPTIMIZER_START_REQUEST(MessageType.COMMAND_1, 0x84),
|
||||
|
||||
TOUCH_SENSOR_GET(MessageType.COMMAND_1, 0xd6),
|
||||
TOUCH_SENSOR_RET(MessageType.COMMAND_1, 0xd7),
|
||||
TOUCH_SENSOR_SET(MessageType.COMMAND_1, 0xd8),
|
||||
TOUCH_SENSOR_NOTIFY(MessageType.COMMAND_1, 0xd9),
|
||||
|
||||
AUDIO_UPSAMPLING_GET(MessageType.COMMAND_1, 0xe6),
|
||||
AUDIO_UPSAMPLING_RET(MessageType.COMMAND_1, 0xe7),
|
||||
AUDIO_UPSAMPLING_SET(MessageType.COMMAND_1, 0xe8),
|
||||
AUDIO_UPSAMPLING_NOTIFY(MessageType.COMMAND_1, 0xe9),
|
||||
|
||||
AUTOMATIC_POWER_OFF_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xf6),
|
||||
AUTOMATIC_POWER_OFF_BUTTON_MODE_RET(MessageType.COMMAND_1, 0xf7),
|
||||
AUTOMATIC_POWER_OFF_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xf8),
|
||||
AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xf9),
|
||||
|
||||
// TODO: The headphones spit out a lot of json, analyze it
|
||||
JSON_GET(MessageType.COMMAND_1, 0xc4),
|
||||
JSON_RET(MessageType.COMMAND_1, 0xc9),
|
||||
|
||||
// TODO: App sends those sometimes
|
||||
SOMETHING_GET(MessageType.COMMAND_1, 0x90),
|
||||
SOMETHING_RET(MessageType.COMMAND_1, 0x91),
|
||||
|
||||
VOICE_NOTIFICATIONS_GET(MessageType.COMMAND_2, 0x46),
|
||||
VOICE_NOTIFICATIONS_RET(MessageType.COMMAND_2, 0x47),
|
||||
VOICE_NOTIFICATIONS_SET(MessageType.COMMAND_2, 0x48),
|
||||
VOICE_NOTIFICATIONS_NOTIFY(MessageType.COMMAND_2, 0x49),
|
||||
|
||||
UNKNOWN(MessageType.UNKNOWN, 0xff);
|
||||
|
||||
private final MessageType messageType;
|
||||
private final byte code;
|
||||
|
||||
PayloadType(final MessageType messageType, final int code) {
|
||||
this.messageType = messageType;
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return this.messageType;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PayloadType fromCode(final MessageType messageType, final byte code) {
|
||||
for (final PayloadType payloadType : values()) {
|
||||
if (messageType.equals(payloadType.messageType) && payloadType.code == code) {
|
||||
return payloadType;
|
||||
}
|
||||
}
|
||||
|
||||
return PayloadType.UNKNOWN;
|
||||
}
|
||||
}
|
@ -0,0 +1,829 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents.SonyHeadphonesEnqueueRequestEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.AudioCodec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.VirtualSoundParam;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonyProtocolImplV1.class);
|
||||
|
||||
public SonyProtocolImplV1(GBDevice device) {
|
||||
super(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAmbientSoundControl() {
|
||||
return new Request(
|
||||
PayloadType.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AMBIENT_SOUND_CONTROL_GET.getCode(),
|
||||
(byte) 0x02
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(8);
|
||||
|
||||
buf.put(PayloadType.AMBIENT_SOUND_CONTROL_SET.getCode());
|
||||
buf.put((byte) 0x02);
|
||||
if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x00);
|
||||
} else {
|
||||
buf.put((byte) 0x11);
|
||||
}
|
||||
buf.put((byte) 0x01);
|
||||
|
||||
switch (ambientSoundControl.getMode()) {
|
||||
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) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
|
||||
|
||||
switch (ambientSoundControl.getMode()) {
|
||||
case OFF:
|
||||
case AMBIENT_SOUND:
|
||||
buf.put((byte) (ambientSoundControl.getAmbientSound() + 1));
|
||||
break;
|
||||
case WIND_NOISE_REDUCTION:
|
||||
case NOISE_CANCELLING:
|
||||
buf.put((byte) 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return new Request(PayloadType.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAudioCodec() {
|
||||
return new Request(
|
||||
PayloadType.AUDIO_CODEC_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AUDIO_CODEC_REQUEST.getCode(),
|
||||
(byte) 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getBattery(BatteryType batteryType) {
|
||||
return new Request(
|
||||
PayloadType.BATTERY_LEVEL_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.BATTERY_LEVEL_REQUEST.getCode(),
|
||||
batteryType.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getFirmwareVersion() {
|
||||
return new Request(
|
||||
PayloadType.FW_VERSION_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.FW_VERSION_REQUEST.getCode(),
|
||||
(byte) 0x02
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAudioUpsampling() {
|
||||
return new Request(
|
||||
PayloadType.AUDIO_UPSAMPLING_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AUDIO_UPSAMPLING_GET.getCode(),
|
||||
(byte) 0x02
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAudioUpsampling(AudioUpsampling config) {
|
||||
return new Request(
|
||||
PayloadType.AUDIO_UPSAMPLING_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AUDIO_UPSAMPLING_SET.getCode(),
|
||||
(byte) 0x02,
|
||||
(byte) 0x00,
|
||||
(byte) (config.isEnabled() ? 0x01 : 0x00)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAutomaticPowerOff() {
|
||||
return new Request(
|
||||
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x04
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAutomaticPowerOff(final AutomaticPowerOff config) {
|
||||
return new Request(
|
||||
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x04,
|
||||
(byte) 0x01,
|
||||
config.getCode()[0],
|
||||
config.getCode()[1]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getEqualizer() {
|
||||
return new Request(
|
||||
PayloadType.EQUALIZER_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.EQUALIZER_GET.getCode(),
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setEqualizerPreset(final EqualizerPreset config) {
|
||||
return new Request(
|
||||
PayloadType.EQUALIZER_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.EQUALIZER_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
config.getCode(),
|
||||
(byte) 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
|
||||
buf.put(PayloadType.EQUALIZER_SET.getCode());
|
||||
buf.put((byte) 0x01);
|
||||
buf.put((byte) 0xff);
|
||||
buf.put((byte) 0x06);
|
||||
|
||||
buf.put((byte) (config.getBass() + 10));
|
||||
for (final Integer band : config.getBands()) {
|
||||
buf.put((byte) (band + 10));
|
||||
}
|
||||
|
||||
return new Request(
|
||||
PayloadType.EQUALIZER_SET.getMessageType(),
|
||||
buf.array()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSoundPosition() {
|
||||
return new Request(
|
||||
PayloadType.SOUND_POSITION_OR_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.SOUND_POSITION_OR_MODE_GET.getCode(),
|
||||
(byte) 0x02
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSoundPosition(final SoundPosition config) {
|
||||
return new Request(
|
||||
PayloadType.SOUND_POSITION_OR_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.SOUND_POSITION_OR_MODE_SET.getCode(),
|
||||
VirtualSoundParam.SOUND_POSITION.getCode(),
|
||||
config.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSurroundMode() {
|
||||
return new Request(
|
||||
PayloadType.SOUND_POSITION_OR_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.SOUND_POSITION_OR_MODE_GET.getCode(),
|
||||
VirtualSoundParam.SURROUND_MODE.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSurroundMode(final SurroundMode config) {
|
||||
return new Request(
|
||||
PayloadType.SOUND_POSITION_OR_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.SOUND_POSITION_OR_MODE_SET.getCode(),
|
||||
VirtualSoundParam.SURROUND_MODE.getCode(),
|
||||
config.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getTouchSensor() {
|
||||
return new Request(
|
||||
PayloadType.TOUCH_SENSOR_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.TOUCH_SENSOR_GET.getCode(),
|
||||
(byte) 0xd2
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setTouchSensor(final TouchSensor config) {
|
||||
return new Request(
|
||||
PayloadType.TOUCH_SENSOR_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.TOUCH_SENSOR_SET.getCode(),
|
||||
(byte) 0xd2,
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x01 : 0x00)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getVoiceNotifications() {
|
||||
return new Request(
|
||||
PayloadType.VOICE_NOTIFICATIONS_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.VOICE_NOTIFICATIONS_GET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setVoiceNotifications(final VoiceNotifications config) {
|
||||
return new Request(
|
||||
PayloadType.VOICE_NOTIFICATIONS_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.VOICE_NOTIFICATIONS_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x01 : 0x00)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request startNoiseCancellingOptimizer() {
|
||||
return new Request(
|
||||
PayloadType.NOISE_CANCELLING_OPTIMIZER_START_REQUEST.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadType.NOISE_CANCELLING_OPTIMIZER_START_REQUEST.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) 0x00,
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
|
||||
final PayloadType payloadType = PayloadType.fromCode(messageType, payload[0]);
|
||||
|
||||
switch (payloadType) {
|
||||
case INIT_REPLY:
|
||||
return handleInitResponse(payload);
|
||||
case FW_VERSION_REPLY:
|
||||
return handleFirmwareVersion(payload);
|
||||
case BATTERY_LEVEL_REPLY:
|
||||
case BATTERY_LEVEL_NOTIFY:
|
||||
return handleBattery(payload);
|
||||
case AUDIO_CODEC_REPLY:
|
||||
case AUDIO_CODEC_NOTIFY:
|
||||
return handleAudioCodec(payload);
|
||||
case SOUND_POSITION_OR_MODE_RET:
|
||||
case SOUND_POSITION_OR_MODE_NOTIFY:
|
||||
return handleVirtualSound(payload);
|
||||
case EQUALIZER_RET:
|
||||
case EQUALIZER_NOTIFY:
|
||||
return handleEqualizer(payload);
|
||||
case AMBIENT_SOUND_CONTROL_RET:
|
||||
case AMBIENT_SOUND_CONTROL_NOTIFY:
|
||||
return handleAmbientSoundControl(payload);
|
||||
case TOUCH_SENSOR_RET:
|
||||
case TOUCH_SENSOR_NOTIFY:
|
||||
return handleTouchSensor(payload);
|
||||
case AUDIO_UPSAMPLING_RET:
|
||||
case AUDIO_UPSAMPLING_NOTIFY:
|
||||
return handleAudioUpsampling(payload);
|
||||
case AUTOMATIC_POWER_OFF_BUTTON_MODE_RET:
|
||||
case AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY:
|
||||
return handleAutomaticPowerOff(payload);
|
||||
case VOICE_NOTIFICATIONS_RET:
|
||||
case VOICE_NOTIFICATIONS_NOTIFY:
|
||||
return handleVoiceNotifications(payload);
|
||||
case JSON_RET:
|
||||
return handleJson(payload);
|
||||
}
|
||||
|
||||
LOG.warn("Unhandled payload type code {}", String.format("%02x", payload[0]));
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleInitResponse(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final DeviceType deviceType = getDevice().getType();
|
||||
|
||||
final List<Request> capabilityRequests = new ArrayList<>();
|
||||
|
||||
// Populate the init requests
|
||||
// TODO: We should be determine which of these we need from the device...
|
||||
switch (deviceType) {
|
||||
case SONY_WH_1000XM3:
|
||||
capabilityRequests.add(getFirmwareVersion());
|
||||
capabilityRequests.add(getBattery(BatteryType.SINGLE));
|
||||
capabilityRequests.add(getAudioCodec());
|
||||
capabilityRequests.add(getAmbientSoundControl());
|
||||
capabilityRequests.add(getAudioUpsampling());
|
||||
capabilityRequests.add(getVoiceNotifications());
|
||||
capabilityRequests.add(getAutomaticPowerOff());
|
||||
capabilityRequests.add(getTouchSensor());
|
||||
capabilityRequests.add(getSurroundMode());
|
||||
capabilityRequests.add(getSoundPosition());
|
||||
capabilityRequests.add(getEqualizer());
|
||||
break;
|
||||
default:
|
||||
LOG.error("Unsupported Sony device type '{}' with key '{}'", deviceType, deviceType.getKey());
|
||||
return null;
|
||||
}
|
||||
|
||||
return Collections.singletonList(new SonyHeadphonesEnqueueRequestEvent(capabilityRequests));
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
|
||||
if (payload.length != 8) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
AmbientSoundControl.Mode mode = null;
|
||||
|
||||
if (payload[2] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.OFF;
|
||||
} else if (payload[2] == (byte) 0x01) {
|
||||
// Enabled, determine mode
|
||||
|
||||
if (payload[4] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
|
||||
} else if (payload[4] == (byte) 0x01) {
|
||||
// FIXME: ANC gets incorrectly identified wind reduction for WF-SP800N
|
||||
mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
|
||||
} else if (payload[4] == (byte) 0x02) {
|
||||
mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unable to determine ambient sound control mode from {}", GB.hexdump(payload));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean focusOnVoice;
|
||||
switch (payload[6]) {
|
||||
case 0x00:
|
||||
focusOnVoice = false;
|
||||
break;
|
||||
case 0x01:
|
||||
focusOnVoice = true;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[6]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int ambientSound = payload[7];
|
||||
if (ambientSound < 0 || ambientSound > 20) {
|
||||
LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[7]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AmbientSoundControl ambientSoundControl = new AmbientSoundControl(mode, focusOnVoice, ambientSound);
|
||||
|
||||
LOG.warn("Ambient sound control: {}", ambientSoundControl);
|
||||
|
||||
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(ambientSoundControl.toPreferences());
|
||||
|
||||
return Collections.singletonList(eventUpdatePreferences);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAudioUpsampling(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean enabled;
|
||||
|
||||
switch (payload[3]) {
|
||||
case 0x00:
|
||||
enabled = false;
|
||||
break;
|
||||
case 0x01:
|
||||
enabled = true;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown audio upsampling code {}", String.format("%02x", payload[3]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Audio Upsampling: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new AudioUpsampling(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAutomaticPowerOff(final byte[] payload) {
|
||||
if (payload.length != 5) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x04) {
|
||||
// TODO: Handle these (Button Mode uses the same payload type?)
|
||||
LOG.warn("Not automatic power off config, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
AutomaticPowerOff mode = null;
|
||||
|
||||
for (AutomaticPowerOff value : AutomaticPowerOff.values()) {
|
||||
if (value.getCode()[0] == payload[3] && value.getCode()[1] == payload[4]) {
|
||||
mode = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown automatic power off codes {}", String.format("%02x %02x", payload[3], payload[4]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Automatic Power Off: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleBattery(final byte[] payload) {
|
||||
final BatteryType batteryType = BatteryType.fromCode(payload[1]);
|
||||
|
||||
if (batteryType == null) {
|
||||
LOG.warn("Unknown battery type code {}", String.format("%02x", payload[1]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final List<GBDeviceEventBatteryInfo> batteryEvents = new ArrayList<>();
|
||||
|
||||
if (BatteryType.SINGLE.equals(batteryType) || BatteryType.CASE.equals(batteryType)) {
|
||||
// Single battery / Case battery
|
||||
LOG.debug("Battery Level: {}: {}", batteryType, payload[2]);
|
||||
|
||||
final GBDeviceEventBatteryInfo singleBatteryInfo = new GBDeviceEventBatteryInfo();
|
||||
singleBatteryInfo.batteryIndex = 0;
|
||||
singleBatteryInfo.state = BatteryState.BATTERY_NORMAL;
|
||||
singleBatteryInfo.level = payload[2];
|
||||
|
||||
batteryEvents.add(singleBatteryInfo);
|
||||
} else if (BatteryType.DUAL.equals(batteryType)) {
|
||||
// Dual Battery (L / R)
|
||||
LOG.debug("Battery Level: L: {}, R: {}", payload[2], payload[4]);
|
||||
|
||||
if (payload[2] != 0) {
|
||||
final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoLeft = new GBDeviceEventBatteryInfo();
|
||||
|
||||
gbDeviceEventBatteryInfoLeft.batteryIndex = 1;
|
||||
gbDeviceEventBatteryInfoLeft.state = BatteryState.BATTERY_NORMAL;
|
||||
gbDeviceEventBatteryInfoLeft.level = payload[2];
|
||||
|
||||
batteryEvents.add(gbDeviceEventBatteryInfoLeft);
|
||||
}
|
||||
|
||||
if (payload[4] != 0) {
|
||||
final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoRight = new GBDeviceEventBatteryInfo();
|
||||
|
||||
gbDeviceEventBatteryInfoRight.batteryIndex = 2;
|
||||
gbDeviceEventBatteryInfoRight.state = BatteryState.BATTERY_NORMAL;
|
||||
gbDeviceEventBatteryInfoRight.level = payload[4];
|
||||
|
||||
batteryEvents.add(gbDeviceEventBatteryInfoRight);
|
||||
}
|
||||
}
|
||||
|
||||
return batteryEvents;
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAudioCodec(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AudioCodec audioCodec = AudioCodec.fromCode(payload[2]);
|
||||
|
||||
if (audioCodec == null) {
|
||||
LOG.warn("Unable to determine audio codec from {}", GB.hexdump(payload));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final GBDeviceEventUpdateDeviceInfo gbDeviceEventUpdateDeviceInfo = new GBDeviceEventUpdateDeviceInfo("AUDIO_CODEC: ", audioCodec.name());
|
||||
|
||||
final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences()
|
||||
.withPreference(DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_CODEC, audioCodec.name().toLowerCase(Locale.getDefault()));
|
||||
|
||||
return Arrays.asList(gbDeviceEventUpdateDeviceInfo, gbDeviceEventUpdatePreferences);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleEqualizer(final byte[] payload) {
|
||||
if (payload.length != 10) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
EqualizerPreset mode = null;
|
||||
for (EqualizerPreset value : EqualizerPreset.values()) {
|
||||
if (value.getCode() == payload[2]) {
|
||||
mode = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown equalizer preset code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Equalizer Preset: {}", mode);
|
||||
|
||||
final int clearBass = payload[4] - 10;
|
||||
final List<Integer> bands = new ArrayList<>(5);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
bands.add(payload[5 + i] - 10);
|
||||
}
|
||||
|
||||
final EqualizerCustomBands customBands = new EqualizerCustomBands(bands, clearBass);
|
||||
|
||||
LOG.info("Equalizer Custom Bands: {}", customBands);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences())
|
||||
.withPreferences(customBands.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleFirmwareVersion(final byte[] payload) {
|
||||
final Pattern VERSION_REGEX = Pattern.compile("^[0-9.\\-a-zA-Z_]+$");
|
||||
|
||||
if (payload.length < 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final String firmwareVersion = new String(Arrays.copyOfRange(payload, 3, payload.length));
|
||||
|
||||
if (!VERSION_REGEX.matcher(firmwareVersion).find()) {
|
||||
LOG.warn("Unexpected characters in version '{}'", firmwareVersion);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Firmware Version: {}", firmwareVersion);
|
||||
|
||||
final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
|
||||
gbDeviceEventVersionInfo.fwVersion = firmwareVersion;
|
||||
return Collections.singletonList(gbDeviceEventVersionInfo);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleJson(final byte[] payload) {
|
||||
// TODO analyze json?
|
||||
|
||||
if (payload.length < 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final String jsonString = new String(Arrays.copyOfRange(payload, 4, payload.length));
|
||||
|
||||
LOG.debug("Got json: {}", jsonString);
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleVirtualSound(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final VirtualSoundParam virtualSoundParam = VirtualSoundParam.fromCode(payload[1]);
|
||||
if (virtualSoundParam == null) {
|
||||
LOG.warn("Unknown payload subtype code {}", String.format("%02x", payload[1]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
switch (virtualSoundParam) {
|
||||
case SURROUND_MODE:
|
||||
return handleSurroundMode(payload);
|
||||
case SOUND_POSITION:
|
||||
return handleSoundPosition(payload);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleSoundPosition(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SoundPosition mode = null;
|
||||
|
||||
for (SoundPosition value : SoundPosition.values()) {
|
||||
if (value.getCode() == payload[2]) {
|
||||
mode = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown sound position code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Sound Position: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleSurroundMode(final byte[] payload) {
|
||||
if (payload.length != 3) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SurroundMode mode = null;
|
||||
|
||||
for (SurroundMode value : SurroundMode.values()) {
|
||||
if (value.getCode() == payload[2]) {
|
||||
mode = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown surround mode code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Surround Mode: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleTouchSensor(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean enabled;
|
||||
|
||||
switch (payload[3]) {
|
||||
case 0x00:
|
||||
enabled = false;
|
||||
break;
|
||||
case 0x01:
|
||||
enabled = true;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown touch sensor code {}", String.format("%02x", payload[3]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Touch Sensor: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new TouchSensor(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean enabled;
|
||||
|
||||
switch (payload[3]) {
|
||||
case 0x00:
|
||||
enabled = false;
|
||||
break;
|
||||
case 0x01:
|
||||
enabled = true;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown voice notifications code {}", String.format("%02x", payload[3]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Voice Notifications: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new VoiceNotifications(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
}
|
@ -14,17 +14,32 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
|
||||
|
||||
public enum ButtonMode {
|
||||
OFF((byte) 0xff),
|
||||
AMBIENT_SOUND_CONTROL((byte) 0x00),
|
||||
PLAYBACK_CONTROL((byte) 0x20),
|
||||
VOLUME_CONTROL((byte) 0x10);
|
||||
public enum AudioCodec {
|
||||
SBC(0x01),
|
||||
AAC(0x02),
|
||||
LDAC(0x10),
|
||||
APTX(0x20),
|
||||
APTX_HD(0x21);
|
||||
|
||||
public final byte code;
|
||||
private final byte code;
|
||||
|
||||
ButtonMode(final byte code) {
|
||||
this.code = code;
|
||||
AudioCodec(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static AudioCodec fromCode(final byte code) {
|
||||
for (final AudioCodec audioCodec : values()) {
|
||||
if (audioCodec.code == code) {
|
||||
return audioCodec;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -14,13 +14,30 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.wh1000xm3;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.SonyHeadphonesProtocol;
|
||||
public enum BatteryType {
|
||||
SINGLE(0x00),
|
||||
DUAL(0x01),
|
||||
CASE(0x02);
|
||||
|
||||
public class SonyWh1000Xm3Protocol extends SonyHeadphonesProtocol {
|
||||
public SonyWh1000Xm3Protocol(GBDevice device) {
|
||||
super(device);
|
||||
private final byte code;
|
||||
|
||||
BatteryType(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static BatteryType fromCode(final byte code) {
|
||||
for (final BatteryType batteryType : values()) {
|
||||
if (batteryType.code == code) {
|
||||
return batteryType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params;
|
||||
|
||||
public enum VirtualSoundParam {
|
||||
SURROUND_MODE(0x01),
|
||||
SOUND_POSITION(0x02),
|
||||
;
|
||||
|
||||
private final byte code;
|
||||
|
||||
VirtualSoundParam(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static VirtualSoundParam fromCode(final byte code) {
|
||||
for (final VirtualSoundParam virtualSound : values()) {
|
||||
if (virtualSound.code == code) {
|
||||
return virtualSound;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -113,7 +113,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.headphones.wh1000xm3.SonyWh1000Xm3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -313,8 +313,8 @@ public class DeviceHelper {
|
||||
result.add(new FitProDeviceCoordinator());
|
||||
result.add(new Ear1Coordinator());
|
||||
result.add(new GalaxyBudsDeviceCoordinator());
|
||||
result.add(new GalaxyBudsLiveDeviceCoordinator());
|
||||
result.add(new SonyWh1000Xm3Coordinator());
|
||||
result.add(new GalaxyBudsLiveDeviceCoordinator());
|
||||
result.add(new SonyWH1000XM3Coordinator());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1401,7 +1401,7 @@
|
||||
<string name="sony_equalizer_preset_manual">Manual</string>
|
||||
<string name="sony_equalizer_preset_custom_1">Custom 1</string>
|
||||
<string name="sony_equalizer_preset_custom_2">Custom 2</string>
|
||||
<string name="pref_header_sony_equalizer_preset_manual">Manual Preset</string>
|
||||
<string name="pref_header_sony_equalizer_bands">Bands</string>
|
||||
<string name="pref_header_sony_equalizer_preset_custom_1">Custom Preset 1</string>
|
||||
<string name="pref_header_sony_equalizer_preset_custom_2">Custom Preset 2</string>
|
||||
<string name="sony_equalizer_band_400">400</string>
|
||||
@ -1410,7 +1410,7 @@
|
||||
<string name="sony_equalizer_band_6300">6.3k</string>
|
||||
<string name="sony_equalizer_band_16000">16k</string>
|
||||
<string name="sony_equalizer_clear_bass">Clear Bass</string>
|
||||
<string name="sony_dsee_hx">DSEE HX</string>
|
||||
<string name="sony_audio_upsampling">Audio Upsampling</string>
|
||||
<string name="sony_touch_sensor">Touch sensor control</string>
|
||||
<string name="sony_notification_voice_guide">Notifications & Voice Guide</string>
|
||||
<string name="sony_automatic_power_off">Automatic Power Off</string>
|
||||
|
11
app/src/main/res/xml/devicesettings_automatic_power_off.xml
Normal file
11
app/src/main/res/xml/devicesettings_automatic_power_off.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_automatic_power_off_names"
|
||||
android:entryValues="@array/sony_automatic_power_off_values"
|
||||
android:icon="@drawable/ic_power_settings_new"
|
||||
android:key="pref_sony_automatic_power_off"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_automatic_power_off" />
|
||||
</androidx.preference.PreferenceScreen>
|
6
app/src/main/res/xml/devicesettings_header_other.xml
Normal file
6
app/src/main/res/xml/devicesettings_header_other.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_other"
|
||||
android:title="@string/pref_header_other" />
|
||||
</androidx.preference.PreferenceScreen>
|
6
app/src/main/res/xml/devicesettings_header_system.xml
Normal file
6
app/src/main/res/xml/devicesettings_header_system.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_other"
|
||||
android:title="@string/pref_header_system" />
|
||||
</androidx.preference.PreferenceScreen>
|
@ -13,6 +13,7 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_ambient_sound" />
|
||||
|
||||
<!-- [0, 19], which maps to [1, 20] on the device, as we can't configure the min on the current API level -->
|
||||
<SeekBarPreference
|
||||
android:defaultValue="0"
|
||||
android:icon="@drawable/ic_volume_up"
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:key="pref_sony_audio_upsampling"
|
||||
android:title="@string/sony_audio_upsampling" />
|
||||
</androidx.preference.PreferenceScreen>
|
@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_key_equalizer"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_equalizer">
|
||||
|
||||
<ListPreference
|
||||
@ -13,151 +16,43 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_equalizer" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:key="pref_sony_equalizer_preset_manual"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_manual">
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_equalizer"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_manual">
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceScreen>
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_manual_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_manual_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_manual_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_manual_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_manual_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_manual_clear_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:key="pref_sony_equalizer_preset_custom_1"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_custom_1">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_equalizer"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_custom_1">
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_1_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_1_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_1_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_1_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_1_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_custom_1_clear_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:key="pref_sony_equalizer_preset_custom_2"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_custom_2">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_equalizer"
|
||||
android:title="@string/pref_header_sony_equalizer_preset_custom_2">
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_2_band_400"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_400" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_2_band_1000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_1000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_2_band_2500"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_2500" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_2_band_6300"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_6300" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_graphic_eq"
|
||||
android:key="pref_sony_equalizer_custom_2_band_16000"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_band_16000" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="10"
|
||||
android:icon="@drawable/ic_speaker"
|
||||
android:key="pref_sony_equalizer_custom_2_clear_bass"
|
||||
android:max="20"
|
||||
android:title="@string/sony_equalizer_clear_bass" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
</PreferenceCategory>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_notifications"
|
||||
android:key="pref_sony_notification_voice_guide"
|
||||
android:title="@string/sony_notification_voice_guide" />
|
||||
</androidx.preference.PreferenceScreen>
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_other"
|
||||
android:title="@string/pref_header_other">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_sound_position_names"
|
||||
android:entryValues="@array/sony_sound_position_values"
|
||||
android:icon="@drawable/ic_switch_left"
|
||||
android:key="pref_sony_sound_position"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_sound_position" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_surround_mode_names"
|
||||
android:entryValues="@array/sony_surround_mode_values"
|
||||
android:icon="@drawable/ic_surround"
|
||||
android:key="pref_sony_surround_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_surround_mode" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:key="pref_sony_dsee_hx"
|
||||
android:title="@string/sony_dsee_hx" />
|
||||
</PreferenceCategory>
|
||||
</androidx.preference.PreferenceScreen>
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_sound_position_names"
|
||||
android:entryValues="@array/sony_sound_position_values"
|
||||
android:icon="@drawable/ic_switch_left"
|
||||
android:key="pref_sony_sound_position"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_sound_position" />
|
||||
</androidx.preference.PreferenceScreen>
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ListPreference
|
||||
android:defaultValue="off"
|
||||
android:entries="@array/sony_surround_mode_names"
|
||||
android:entryValues="@array/sony_surround_mode_values"
|
||||
android:icon="@drawable/ic_surround"
|
||||
android:key="pref_sony_surround_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/sony_surround_mode" />
|
||||
</androidx.preference.PreferenceScreen>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_touch"
|
||||
android:key="pref_sony_touch_sensor"
|
||||
android:title="@string/sony_touch_sensor" />
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user