mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-03 17:02:13 +01:00
Merge branch 'master' of codeberg.org:Freeyourgadget/Gadgetbridge
This commit is contained in:
commit
a1f9fe82d2
@ -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);
|
||||
|
@ -97,6 +97,7 @@ public class AmazfitBipUCoordinator extends HuamiCoordinator {
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitbipu,
|
||||
//R.xml.devicesettings_canned_dismisscall_16,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_custom_emoji_font,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -78,6 +78,7 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
// Telegram
|
||||
put("org.telegram.messenger", NotificationType.TELEGRAM);
|
||||
put("org.telegram.messenger.beta", NotificationType.TELEGRAM);
|
||||
put("org.telegram.messenger.web", NotificationType.TELEGRAM);
|
||||
put("org.telegram.plus", NotificationType.TELEGRAM); // "Plus Messenger"
|
||||
put("org.thunderdog.challegram", NotificationType.TELEGRAM);
|
||||
put("nekox.messenger", NotificationType.TELEGRAM);
|
||||
@ -170,6 +171,9 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
|
||||
// Etar
|
||||
put("ws.xsoh.etar", NotificationType.GENERIC_CALENDAR);
|
||||
|
||||
// Discord
|
||||
put("com.discord", NotificationType.DISCORD);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ public enum NotificationType {
|
||||
THREEMA(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
|
||||
KONTALK(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
|
||||
ANTOX(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.JaegerGreen),
|
||||
DISCORD(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Purpureus),
|
||||
TRANSIT(PebbleIconID.LOCATION, PebbleColor.JaegerGreen),
|
||||
TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon),
|
||||
VIBER(PebbleIconID.NOTIFICATION_VIBER, PebbleColor.VividViolet),
|
||||
@ -122,6 +123,7 @@ public enum NotificationType {
|
||||
case SLACK:
|
||||
case LINE:
|
||||
case VIBER:
|
||||
case DISCORD:
|
||||
return "generic_chat";
|
||||
case GMAIL:
|
||||
case GOOGLE_INBOX:
|
||||
|
@ -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();
|
||||
|
@ -64,28 +64,28 @@ public class HuamiIcon {
|
||||
public static byte mapToIconId(NotificationType type) {
|
||||
switch (type) {
|
||||
case UNKNOWN:
|
||||
case GENERIC_NAVIGATION:
|
||||
return APP_11;
|
||||
case CONVERSATIONS:
|
||||
case RIOT:
|
||||
case HIPCHAT:
|
||||
case KONTALK:
|
||||
case ANTOX:
|
||||
case GENERIC_SMS:
|
||||
case WECHAT:
|
||||
return WECHAT;
|
||||
case GENERIC_EMAIL:
|
||||
case GMAIL:
|
||||
case YAHOO_MAIL:
|
||||
case OUTLOOK:
|
||||
return EMAIL;
|
||||
case GENERIC_NAVIGATION:
|
||||
return APP_11;
|
||||
case GENERIC_SMS:
|
||||
return WECHAT;
|
||||
case GENERIC_CALENDAR:
|
||||
case BUSINESS_CALENDAR:
|
||||
return CALENDAR;
|
||||
case FACEBOOK:
|
||||
return FACEBOOK;
|
||||
case FACEBOOK_MESSENGER:
|
||||
case SIGNAL:
|
||||
return FACEBOOK_MESSENGER;
|
||||
case GOOGLE_HANGOUTS:
|
||||
case GOOGLE_MESSENGER:
|
||||
@ -97,9 +97,8 @@ public class HuamiIcon {
|
||||
return KAKAOTALK;
|
||||
case LINE:
|
||||
return LINE;
|
||||
case SIGNAL:
|
||||
return FACEBOOK_MESSENGER;
|
||||
case WIRE:
|
||||
case THREEMA:
|
||||
return CHAT_BLUE_13;
|
||||
case TWITTER:
|
||||
return TWITTER;
|
||||
@ -109,12 +108,9 @@ public class HuamiIcon {
|
||||
return SNAPCHAT;
|
||||
case TELEGRAM:
|
||||
return TELEGRAM;
|
||||
case THREEMA:
|
||||
return CHAT_BLUE_13;
|
||||
case VIBER:
|
||||
case DISCORD:
|
||||
return VIBER;
|
||||
case WECHAT:
|
||||
return WECHAT;
|
||||
case WHATSAPP:
|
||||
return WHATSAPP;
|
||||
case GENERIC_ALARM_CLOCK:
|
||||
@ -122,4 +118,30 @@ public class HuamiIcon {
|
||||
}
|
||||
return APP_11;
|
||||
}
|
||||
|
||||
//amazfit workaround
|
||||
public static boolean acceptsSender(byte iconId){
|
||||
switch(iconId){
|
||||
case WECHAT:
|
||||
case PENGUIN_1:
|
||||
case MI_CHAT_2:
|
||||
case SNAPCHAT:
|
||||
case WHATSAPP:
|
||||
case RED_WHITE_FIRE_8:
|
||||
case INSTAGRAM:
|
||||
case CHAT_BLUE_13:
|
||||
case COW_14:
|
||||
case CHINESE_20:
|
||||
case FACEBOOK_MESSENGER:
|
||||
case VIBER:
|
||||
case LINE:
|
||||
case TELEGRAM:
|
||||
case VKONTAKTE:
|
||||
case CHINESE_32:
|
||||
case EMAIL:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1004,6 +1004,27 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
if (cannedMessagesSpec.type == CannedMessagesSpec.TYPE_REJECTEDCALLS) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Set canned messages");
|
||||
|
||||
int handle = 0x12345678;
|
||||
for (String cannedMessage : cannedMessagesSpec.cannedMessages) {
|
||||
int length = cannedMessage.getBytes().length + 5;
|
||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buf.put((byte) 0x05); // create
|
||||
buf.putInt(handle++);
|
||||
buf.put(cannedMessage.getBytes());
|
||||
|
||||
writeToChunked2021(builder, (short) 0x0013, getNextHandle(), buf.array(), false, false);
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to set time on Huami device", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAlarmClockRinging() {
|
||||
@ -2976,7 +2997,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
byte[] command = ArrayUtils.addAll(new byte[]{0x00, 0x00, (byte) (0xc0 | type), 0x00}, data);
|
||||
writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_COMPAT, getNextHandle(), command, encrypt);
|
||||
writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_COMPAT, getNextHandle(), command, true, encrypt);
|
||||
} else {
|
||||
writeToChunkedOld(builder, type, data);
|
||||
}
|
||||
@ -3010,13 +3031,17 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToChunked2021(TransactionBuilder builder, short type, byte handle, byte[] data, boolean encrypt) {
|
||||
public void writeToChunked2021(TransactionBuilder builder, short type, byte handle, byte[] data, boolean extended_flags, boolean encrypt) {
|
||||
int remaining = data.length;
|
||||
int length = data.length;
|
||||
byte count = 0;
|
||||
int header_size = 11;
|
||||
int header_size = 10;
|
||||
|
||||
if (encrypt) {
|
||||
if (extended_flags) {
|
||||
header_size++;
|
||||
}
|
||||
|
||||
if (extended_flags && encrypt) {
|
||||
byte[] messagekey = new byte[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
messagekey[i] = (byte) (sharedSessionKey[i] ^ handle);
|
||||
@ -3060,26 +3085,40 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
if (count == 0) {
|
||||
flags |= 0x01;
|
||||
chunk[5] = (byte) (length & 0xff);
|
||||
chunk[6] = (byte) ((length >> 8) & 0xff);
|
||||
chunk[7] = (byte) ((length >> 16) & 0xff);
|
||||
chunk[8] = (byte) ((length >> 24) & 0xff);
|
||||
chunk[9] = (byte) (type & 0xff);
|
||||
chunk[10] = (byte) ((type >> 8) & 0xff);
|
||||
int i = 4;
|
||||
if (extended_flags) {
|
||||
i++;
|
||||
}
|
||||
chunk[i++] = (byte) (length & 0xff);
|
||||
chunk[i++] = (byte) ((length >> 8) & 0xff);
|
||||
chunk[i++] = (byte) ((length >> 16) & 0xff);
|
||||
chunk[i++] = (byte) ((length >> 24) & 0xff);
|
||||
chunk[i++] = (byte) (type & 0xff);
|
||||
chunk[i] = (byte) ((type >> 8) & 0xff);
|
||||
}
|
||||
if (remaining <= MAX_CHUNKLENGTH) {
|
||||
flags |= 0x06; // last chunk?
|
||||
}
|
||||
chunk[0] = 0x03;
|
||||
chunk[1] = flags;
|
||||
chunk[2] = 0;
|
||||
chunk[3] = handle;
|
||||
chunk[4] = count;
|
||||
if (extended_flags) {
|
||||
chunk[2] = 0;
|
||||
chunk[3] = handle;
|
||||
chunk[4] = count;
|
||||
} else {
|
||||
chunk[2] = handle;
|
||||
chunk[3] = count;
|
||||
}
|
||||
|
||||
System.arraycopy(data, data.length - remaining, chunk, header_size, copybytes);
|
||||
builder.write(characteristicChunked2021Write, chunk);
|
||||
remaining -= copybytes;
|
||||
header_size = 5;
|
||||
header_size = 4;
|
||||
|
||||
if (extended_flags) {
|
||||
header_size++;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@ -3087,7 +3126,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
public void writeToConfiguration(TransactionBuilder builder, byte[] data) {
|
||||
if (force2021Protocol) {
|
||||
data = ArrayUtils.insert(0, data, (byte) 1);
|
||||
writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_COMPAT, getNextHandle(), data, true);
|
||||
writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_COMPAT, getNextHandle(), data, true, true);
|
||||
} else {
|
||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
}
|
||||
|
@ -18,17 +18,32 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2MiniFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class AmazfitGTS2MiniSupport extends AmazfitGTS2Support {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTS2MiniSupport.class);
|
||||
|
||||
@Override
|
||||
protected HuamiSupport setLanguage(TransactionBuilder builder) {
|
||||
return setLanguageByIdNew(builder);
|
||||
@ -38,4 +53,122 @@ public class AmazfitGTS2MiniSupport extends AmazfitGTS2Support {
|
||||
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitGTS2MiniFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendNotificationNew(NotificationSpec notificationSpec, boolean hasExtraHeader, int maxLength) {
|
||||
// step 1: bail out if this is an alarm clock notification
|
||||
|
||||
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
||||
onAlarmClock(notificationSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
// step 2: (formerly in try block) get notification type
|
||||
AlertCategory alertCategory = AlertCategory.CustomHuami;
|
||||
byte customIconId = HuamiIcon.mapToIconId(notificationSpec.type);
|
||||
|
||||
// step 3: build notification (sender+body)
|
||||
/*
|
||||
* Format followed by the device:
|
||||
* <SENDER> \0 <BODY> \0 <APP SUFFIX>
|
||||
* sender will get ignored except for the icons
|
||||
* specified on the HuamiIcon class.
|
||||
* for email, App Suffix will be taken as sender
|
||||
*/
|
||||
String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
|
||||
boolean acceptsSender = HuamiIcon.acceptsSender(customIconId);
|
||||
String message;
|
||||
|
||||
if (!acceptsSender && !senderOrTitle.equals(notificationSpec.sourceName)) {
|
||||
// make sure we always include the notification sender/title
|
||||
message = "-\0"; //leave title blank, it's useless
|
||||
message += StringUtils.truncate(senderOrTitle, 64) + "\n";
|
||||
} else {
|
||||
message = StringUtils.truncate(senderOrTitle, 64) + "\0";
|
||||
}
|
||||
|
||||
if (notificationSpec.subject != null) {
|
||||
message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n";
|
||||
}
|
||||
if (notificationSpec.body != null) {
|
||||
message += StringUtils.truncate(notificationSpec.body, 512);
|
||||
}
|
||||
if (notificationSpec.body == null && notificationSpec.subject == null) {
|
||||
message += " "; // if we have no body we have to send at least something on some devices, else they reboot (Bip S)
|
||||
}
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("new notification");
|
||||
|
||||
// step 4: append suffix
|
||||
byte[] appSuffix = "\0 \0".getBytes();
|
||||
int suffixlength = appSuffix.length;
|
||||
// The SMS icon for AlertCategory.SMS is unique and not available as iconId
|
||||
if (notificationSpec.type == NotificationType.GENERIC_SMS) {
|
||||
alertCategory = AlertCategory.SMS;
|
||||
}
|
||||
// EMAIL icon does not work in FW 0.0.8.74, it did in 0.0.7.90 (old comment)
|
||||
// EMAIL will take the sender from the suffix instead
|
||||
else if (customIconId == HuamiIcon.EMAIL) {
|
||||
alertCategory = AlertCategory.Email;
|
||||
appSuffix = ("\0"+senderOrTitle+"\0").getBytes();
|
||||
suffixlength = appSuffix.length;
|
||||
}
|
||||
|
||||
// if I understood correctly, we don't need the extra logic for mi band 2 here
|
||||
int prefixlength = 2;
|
||||
|
||||
if (alertCategory == AlertCategory.CustomHuami) {
|
||||
String appName;
|
||||
prefixlength = 3;
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
ApplicationInfo ai = null;
|
||||
try {
|
||||
ai = pm.getApplicationInfo(notificationSpec.sourceAppId, 0);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
}
|
||||
|
||||
if (ai == null) {
|
||||
appName = "\0" + "UNKNOWN" + "\0";
|
||||
} else {
|
||||
appName = "\0" + pm.getApplicationLabel(ai) + "\0";
|
||||
}
|
||||
appSuffix = appName.getBytes();
|
||||
suffixlength = appSuffix.length;
|
||||
}
|
||||
if (hasExtraHeader) {
|
||||
prefixlength += 4;
|
||||
}
|
||||
|
||||
// final step: build command
|
||||
byte[] rawmessage = message.getBytes();
|
||||
int length = Math.min(rawmessage.length, maxLength - prefixlength);
|
||||
if (length < rawmessage.length) {
|
||||
length = StringUtils.utf8ByteLength(message, length);
|
||||
}
|
||||
|
||||
byte[] command = new byte[length + prefixlength + suffixlength];
|
||||
int pos = 0;
|
||||
command[pos++] = (byte) alertCategory.getId();
|
||||
if (hasExtraHeader) {
|
||||
command[pos++] = 0; // TODO
|
||||
command[pos++] = 0;
|
||||
command[pos++] = 0;
|
||||
command[pos++] = 0;
|
||||
}
|
||||
command[pos++] = 1;
|
||||
if (alertCategory == AlertCategory.CustomHuami) {
|
||||
command[pos] = customIconId;
|
||||
}
|
||||
|
||||
System.arraycopy(rawmessage, 0, command, prefixlength, length);
|
||||
System.arraycopy(appSuffix, 0, command, prefixlength + length, appSuffix.length);
|
||||
|
||||
writeToChunked(builder, 0, command);
|
||||
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send notification to device", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public class InitOperation2021 extends InitOperation {
|
||||
sendPubkeyCommand[3] = 0x02;
|
||||
System.arraycopy(publicEC, 0, sendPubkeyCommand, 4, 48);
|
||||
//testAuth();
|
||||
huamiSupport.writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_AUTH, huamiSupport.getNextHandle(), sendPubkeyCommand, false);
|
||||
huamiSupport.writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_AUTH, huamiSupport.getNextHandle(), sendPubkeyCommand, true, false);
|
||||
}
|
||||
|
||||
private native byte[] ecdh_generate_public(byte[] privateEC);
|
||||
@ -170,7 +170,7 @@ public class InitOperation2021 extends InitOperation {
|
||||
System.arraycopy(encryptedRandom1, 0, command, 1, 16);
|
||||
System.arraycopy(encryptedRandom2, 0, command, 17, 16);
|
||||
TransactionBuilder builder = createTransactionBuilder("Sending double encryted random to device");
|
||||
huamiSupport.writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_AUTH, huamiSupport.getNextHandle(), command, false);
|
||||
huamiSupport.writeToChunked2021(builder, HuamiService.CHUNKED2021_ENDPOINT_AUTH, huamiSupport.getNextHandle(), command, true, false);
|
||||
huamiSupport.performImmediately(builder);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@
|
||||
<string name="pref_title_notifications_generic">Suport per a notificacions genèriques</string>
|
||||
<string name="pref_title_whenscreenon">… també amb la pantalla encesa</string>
|
||||
<string name="pref_title_notification_filter">No molesteu</string>
|
||||
<string name="pref_summary_notification_filter">En aquest mode s\'aturen les notifications no desitjades</string>
|
||||
<string name="pref_summary_notification_filter">Blocar totes les notificacions quan el \"No molesteu\" estigui activat al telèfon</string>
|
||||
<string name="pref_title_transliteration">Transliteració</string>
|
||||
<string name="pref_summary_transliteration">Activeu aquesta opció si el vostre aparell no pot mostrar els caràcters de la vostra llengua</string>
|
||||
<string name="always">Sempre</string>
|
||||
@ -550,10 +550,10 @@
|
||||
<string name="share">Comparteix</string>
|
||||
<string name="reset_index">Reinicialitza la dada d\'extracció</string>
|
||||
<string name="menuitem_status">Estat</string>
|
||||
<string name="menuitem_activity">Activitat</string>
|
||||
<string name="menuitem_activity">Historial d\'entrenaments</string>
|
||||
<string name="menuitem_weather">El Temps</string>
|
||||
<string name="menuitem_alarm">Alarma</string>
|
||||
<string name="menuitem_timer">Temporitzador</string>
|
||||
<string name="menuitem_timer">Compte enrere</string>
|
||||
<string name="menuitem_compass">Brúixola</string>
|
||||
<string name="menuitem_settings">Configuració</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -1001,13 +1001,13 @@
|
||||
<string name="hr_widget_last_notification">Última notificació</string>
|
||||
<string name="menuitem_stopwatch">Cronòmetre</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Fes una foto</string>
|
||||
<string name="menuitem_takephoto">Comandament de la càmera</string>
|
||||
<string name="menuitem_mutephone">Silencia el telèfon</string>
|
||||
<string name="menuitem_findphone">Troba el telèfon</string>
|
||||
<string name="menuitem_worldclock">Rellotge mundial</string>
|
||||
<string name="menuitem_unknown">Desconegut</string>
|
||||
<string name="menuitem_stress">Estrès</string>
|
||||
<string name="menuitem_cycles">Període</string>
|
||||
<string name="menuitem_cycles">Seguiment del cicle</string>
|
||||
<string name="menuitem_breathing">Respiració</string>
|
||||
<string name="devicetype_lefun">Lefun</string>
|
||||
<string name="devicetype_sg2">Lemfo SG2</string>
|
||||
@ -1260,4 +1260,76 @@
|
||||
<string name="watchface_widget_type_steps">Passes</string>
|
||||
<string name="watchface_dialog_widget_type">Tipus:</string>
|
||||
<string name="watchface_dialog_widget_x_coordinate">Coordenada X (màxim 240):</string>
|
||||
<string name="title_activity_set_reminders">Configureu els recordatoris</string>
|
||||
<string name="controlcenter_start_configure_reminders">Configureu els recordatoris</string>
|
||||
<string name="reminder_time">Hora</string>
|
||||
<string name="reminder_time_once">%1$s, una vegada</string>
|
||||
<string name="reminder_time_every_week">%1$s, cada setmana</string>
|
||||
<string name="reminder_once">una vegada</string>
|
||||
<string name="reminder_every_year">Cada any</string>
|
||||
<string name="reminder_delete_confirm_title">Esborrar recordatori</string>
|
||||
<string name="reminder_delete_confirm_description">Esteu segur que voleu esborrar el recordatori\?</string>
|
||||
<string name="reminder_no_free_slots_title">No hi ha espais lliures</string>
|
||||
<string name="reminder_no_free_slots_description">El dispositiu no té espais lliures per a recordatoris (espais totals: %1$s)</string>
|
||||
<string name="title_activity_reminder_details">Detalls del recordatori</string>
|
||||
<string name="heart_rate_result">Resultats de la mesura</string>
|
||||
<string name="miband_prefs_reserve_reminder_calendar">Recordatoris a reservar per esdeveniments futurs</string>
|
||||
<string name="prefs_reserve_reminder_calendar_summary">Nombre d\'esdeveniments del calendari que seran sincronitzats</string>
|
||||
<string name="prefs_sounds_summary">Configureu quan l\'aparell pitarà</string>
|
||||
<string name="prefs_fm_presets_presets">Preconfiguracions</string>
|
||||
<string name="device_card_activity_card_title">Informació d\'activitat a la tarja de l\'aparell</string>
|
||||
<string name="pref_header_notification_application_settings">Configuració per aplicació</string>
|
||||
<string name="pref_title_notification_use_as">Useu la llista d\'aplicacions per a ...</string>
|
||||
<string name="pref_title_notification_use_as_deny">Rebutgeu les notificacions de les aplicacions triades</string>
|
||||
<string name="pref_title_notification_use_as_allow">Permeteu les notificacions de les aplicacions triades</string>
|
||||
<string name="notification_channel_transfer_name">Transferència de dades</string>
|
||||
<string name="notification_channel_low_battery_name">Bateria baixa</string>
|
||||
<string name="reminder_every_day">Cada dia</string>
|
||||
<string name="reminder_message">Missatge</string>
|
||||
<string name="reminder_time_every_day">%1$s, cada dia</string>
|
||||
<string name="prefs_activity_in_device_card_distance_title_summary">La distància es calcula a partir de les passes i la llargada de la passa (configurable a “Configuració - Sobre tu”)</string>
|
||||
<string name="pref_theme_black_background">Useu fons negre amb el Tema Fosc</string>
|
||||
<string name="pref_summary_huami_force_new_protocol">Activeu en cas que l\'aparell no es connecti després d\'una actualització de microprogramari</string>
|
||||
<string name="reminder_repeat">Repetir</string>
|
||||
<string name="reminder_date">Data</string>
|
||||
<string name="reminder_time_every_month">%1$s, cada mes</string>
|
||||
<string name="reminder_time_every_year">%1$s, cada any</string>
|
||||
<string name="reminder_every_month">Cada mes</string>
|
||||
<string name="reminder_every_week">Cada setmana</string>
|
||||
<string name="menuitem_pomodoro">Seguiment Pomodoro</string>
|
||||
<string name="dbmanagementactivity_export_confirmation">Voleu exportar dades\? Les dades d’activitat exportades anteriorment (si n’hi ha) i les preferències se sobreescriuran.</string>
|
||||
<string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
|
||||
<string name="prefs_fm_preset_instructions">Premeu una estona el botó per desar la preconfiguració</string>
|
||||
<string name="check_all_applications">Activar totes les aplicacions</string>
|
||||
<string name="uncheck_all_applications">Desactivar totes les aplicacions</string>
|
||||
<string name="averageStrokeDistance">Llargada mitjana d\'una braçada</string>
|
||||
<string name="toast_app_must_not_be_selected">L\'aplicació no ha d\'estar seleccionar per a ser configurada</string>
|
||||
<string name="toast_app_must_be_selected">L\'aplicació ha d\'estar seleccionar per a ser configurada</string>
|
||||
<string name="qhybrid_calibration_10_steps">10 passes</string>
|
||||
<string name="qhybrid_calibration_100_steps">100 passes</string>
|
||||
<string name="qhybrid_pref_summary_actions">Accions per a l\'aplicació Commute</string>
|
||||
<string name="pref_title_developer_settings">Configuració per a programadors</string>
|
||||
<string name="qhybrid_summary_file_management">Envieu i descarregueu fitxers</string>
|
||||
<string name="pref_summary_physical_buttons">Configureu la funcionalitat dels botons físics del rellotge</string>
|
||||
<string name="bpm">bpm</string>
|
||||
<string name="qhybrid_title_apps_management">Gestió d\'apps</string>
|
||||
<string name="fossil_hr_edit_action">Acció d\'editar</string>
|
||||
<string name="pref_summary_canned_messages_set">Envieu els missatges configurats més avall al vostre aparell</string>
|
||||
<string name="pref_summary_developer_settings">Configuració i funcionalitat usada per programadors</string>
|
||||
<string name="qhybrid_pref_title_external_intents">Permetre el intents externs perillosos</string>
|
||||
<string name="devicetype_smaq2oss">SMA-Q2 OSS</string>
|
||||
<string name="gpx_receiver_overwrite_some_files">Algun(s) fitxer(s) ja existeixen. Sobreescriure\'ls\?</string>
|
||||
<string name="ignore_bonded_devices_description">Activant aquesta opció farà que s\'ignorin els aparells que ja han estat vinculats/aparellats a l\'escanejar</string>
|
||||
<string name="gpx_receiver_files_received">Fitxer(s) GPX rebut(s):</string>
|
||||
<string name="fossil_hr_auth_failed">Autenticació fallada, funcionalitat limitada</string>
|
||||
<string name="fossil_hr_unavailable_unauthed">No disponible en mode no autenticat</string>
|
||||
<string name="qhybrid_calibration_1_step">1 passa</string>
|
||||
<string name="pref_check_permission_status_summary">Comproveu i demaneu els permisos que faltin tot i que poden no ser necessitats en aquest moment. Desactiveu això només si el vostre aparell realment no suporta cap d\'aquestes característiques. No donar un permís pot causar problemes!</string>
|
||||
<string name="fossil_hr_warning_firmware_too_new">Algunes funcions estan desactivades perquè el microprogramari del rellotge és massa nou</string>
|
||||
<string name="companiondevice_pairing_details">Activa el suport de la nova API de CompanionDevice (només té efecte en Android 8 o superior) que incrementarà la fiabilitat si el servei s\'ha de reiniciar en segon pla; requereix re-aparellar amb Gadgetbridge per tenir efecte</string>
|
||||
<string name="error_version_check_extreme_caution">COMPTE: Error al comprovar la informació de versió! No hauríeu de continuar! Nom de versió vist: \"%s\"</string>
|
||||
<string name="averageStrokesPerSecond">Mitjana de braçades</string>
|
||||
<string name="fossil_hr_commute_actions_explanation">Les accions configurades aquí apareixeran a l\'aplicació Commute del rellotge. Llegiu la wiki per obtenir informació sobre com gestionar els intents produïts per aquestes accions.</string>
|
||||
<string name="pref_summary_canned_messages_dismisscall">Ignoreu les trucades del rellotge amb un missatge SMS</string>
|
||||
<string name="pref_title_physical_buttons">Botons físics</string>
|
||||
</resources>
|
@ -514,7 +514,7 @@
|
||||
<string name="menuitem_shortcut_alipay">Alipay (Zástupce)</string>
|
||||
<string name="menuitem_shortcut_weather">Počasí (Zástupce)</string>
|
||||
<string name="menuitem_status">Stav</string>
|
||||
<string name="menuitem_activity">Aktivita</string>
|
||||
<string name="menuitem_activity">Historie Aktivit</string>
|
||||
<string name="menuitem_weather">Počasí</string>
|
||||
<string name="menuitem_alarm">Budík</string>
|
||||
<string name="menuitem_timer">Časovač</string>
|
||||
@ -988,7 +988,7 @@
|
||||
<string name="companiondevice_pairing">Párování systémem Doprovodného Zařízení</string>
|
||||
<string name="require_location_provider">Poloha musí být povolena</string>
|
||||
<string name="menuitem_stress">Stres</string>
|
||||
<string name="menuitem_cycles">Cykly</string>
|
||||
<string name="menuitem_cycles">Záznam cyklistiky</string>
|
||||
<string name="menuitem_breathing">Dýchání</string>
|
||||
<string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
|
||||
<string name="devicetype_tlw64">TLW64</string>
|
||||
@ -1452,4 +1452,46 @@
|
||||
<string name="pref_header_sony_equalizer_preset_manual">Ruční předvolba</string>
|
||||
<string name="pref_header_sony_equalizer_preset_custom_2">Vlastní předvolba 2</string>
|
||||
<string name="sony_automatic_power_off_when_taken_off">Při sundání</string>
|
||||
<string name="title_activity_set_reminders">Upravit připomenutí</string>
|
||||
<string name="controlcenter_start_configure_reminders">Upravit připomenutí</string>
|
||||
<string name="reminder_time_once">%1$s, Jednou</string>
|
||||
<string name="reminder_time_every_week">%1$s, Týdně</string>
|
||||
<string name="reminder_time_every_day">%1$s, Denně</string>
|
||||
<string name="reminder_time_every_month">%1$s, Měsíčně</string>
|
||||
<string name="reminder_every_month">Měsíčně</string>
|
||||
<string name="reminder_every_year">Ročně</string>
|
||||
<string name="reminder_delete_confirm_title">Smazat připomenutí</string>
|
||||
<string name="reminder_delete_confirm_description">Opravdu chcete smazat připomenutí\?</string>
|
||||
<string name="reminder_no_free_slots_title">Nejsou volná místa</string>
|
||||
<string name="reminder_no_free_slots_description">Zařízení nemá volná místa pro další připomenutí (celková místa: %1$s)</string>
|
||||
<string name="title_activity_reminder_details">Detaily připomenutí</string>
|
||||
<string name="miband_prefs_reserve_reminder_calendar">Rezervovaná místa připomenutí pro nadcházející události kalendáře</string>
|
||||
<string name="qhybrid_title_on_device_confirmation">Povolit potvrzení párování na zařízení</string>
|
||||
<string name="prefs_fm_preset_instructions">Dlouhým podržením tlačítka uložte předvolbu</string>
|
||||
<string name="reminder_every_day">Denně</string>
|
||||
<string name="reminder_message">Text</string>
|
||||
<string name="reminder_repeat">Opakování</string>
|
||||
<string name="reminder_time">Čas</string>
|
||||
<string name="reminder_date">Datum</string>
|
||||
<string name="reminder_time_every_year">%1$s, Ročně</string>
|
||||
<string name="reminder_once">Jednou</string>
|
||||
<string name="reminder_every_week">Týdně</string>
|
||||
<string name="prefs_reserve_reminder_calendar_summary">Počet kalendářových událostí k synchronizaci</string>
|
||||
<string name="watchface_dialog_widget_timeout_show_circle">Zobrazit orámování po časovém limitu:</string>
|
||||
<string name="qhybrid_summary_on_device_confirmation">Potvrzení párování na zařízení může být otravné. Jejich vypnutím můžete přijít o funkčnost.</string>
|
||||
<string name="prefs_fm_presets_presets">Předvolby</string>
|
||||
<string name="watchface_dialog_widget_timezone">Časová zóna:</string>
|
||||
<string name="watchface_dialog_widget_update_timeout">Časový limit aktualizace v minutách:</string>
|
||||
<string name="watchface_dialog_widget_timeout_hide_text">Skrytí textu při vypršení časového limitu:</string>
|
||||
<string name="averageAltitude">Průměr</string>
|
||||
<string name="minSpeed">Minimum</string>
|
||||
<string name="maxStride">Max krok</string>
|
||||
<string name="minStride">Min krok</string>
|
||||
<string name="averageCadence">Průměrný rytmus</string>
|
||||
<string name="maxCadence">Max rytmus</string>
|
||||
<string name="spm">kroky/min</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Zapnout displej při zvednutí během režimu Nerušit</string>
|
||||
<string name="minHR">Min Tepová frekvence</string>
|
||||
<string name="maxHR">Max Tepová frekvence</string>
|
||||
<string name="minCadence">Min rytmus</string>
|
||||
</resources>
|
@ -457,7 +457,7 @@
|
||||
\nINSTALLATION AUF EIGENE GEFAHR!</string>
|
||||
<string name="mi2_prefs_button_press_count">Anzahl der Tastendrücke</string>
|
||||
<string name="pref_title_charts_swipe">Streichen nach links/rechts im Diagrammbetrieb aktivieren</string>
|
||||
<string name="pref_summary_pebble_enable_bgjs">Wenn aktiviert, können Zifferblätter Wetter, Akkuinfos, usw. anzeigen</string>
|
||||
<string name="pref_summary_pebble_enable_bgjs">Wenn aktiviert, kann die Uhr Wetter, Akkuinfos, usw. anzeigen</string>
|
||||
<string name="prefs_title_heartrate_measurement_interval">Ganztägige Messung der Herzfrequenz</string>
|
||||
<string name="interval_one_minute">einmal pro Minute</string>
|
||||
<string name="interval_five_minutes">alle 5 Minuten</string>
|
||||
@ -520,10 +520,10 @@
|
||||
<string name="choose_auto_export_location">Speicherort für Export auswählen</string>
|
||||
<string name="notification_channel_name">Allgemein</string>
|
||||
<string name="menuitem_status">Status</string>
|
||||
<string name="menuitem_activity">Aktivität</string>
|
||||
<string name="menuitem_activity">Trainingsverlauf</string>
|
||||
<string name="menuitem_weather">Wetter</string>
|
||||
<string name="menuitem_alarm">Alarm</string>
|
||||
<string name="menuitem_timer">Timer</string>
|
||||
<string name="menuitem_timer">Countdown</string>
|
||||
<string name="menuitem_compass">Kompass</string>
|
||||
<string name="menuitem_settings">Einstellungen</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -764,8 +764,8 @@
|
||||
<string name="widget_listing_label">Status und Wecker</string>
|
||||
<string name="widget_set_alarm_after">Wecker einstellen nach:</string>
|
||||
<plurals name="widget_alarm_target_hours">
|
||||
<item quantity="one">%d Std</item>
|
||||
<item quantity="other">%d Std</item>
|
||||
<item quantity="one">%d Stunde</item>
|
||||
<item quantity="other">%d Stunden</item>
|
||||
</plurals>
|
||||
<string name="preferences_makibes_hr3_settings">Makibes HR3-Einstellungen</string>
|
||||
<string name="devicetype_makibes_hr3">Makibes HR3</string>
|
||||
@ -866,7 +866,7 @@
|
||||
<string name="pref_header_auto_fetch">Automatischer Abruf</string>
|
||||
<string name="pref_qhybrid_save_raw_activity_files">Raw Activity Dateien speichern</string>
|
||||
<string name="menuitem_workout">Training</string>
|
||||
<string name="menuitem_hr">Herzfrequenz</string>
|
||||
<string name="menuitem_hr">Puls</string>
|
||||
<string name="devicetype_watchxplus">Watch X Plus</string>
|
||||
<string name="devicetype_watchx">Watch X</string>
|
||||
<string name="prefs_sensors_button_bp_calibration">Kalibrierung</string>
|
||||
@ -886,7 +886,7 @@
|
||||
<string name="power_mode_title">Uhr-Energiesparmodus</string>
|
||||
<string name="menuitem_eventreminder">Ereigniserinnerung</string>
|
||||
<string name="menuitem_pai">PAI</string>
|
||||
<string name="pref_summary_device_spec_settings_title_force_time">Erzwinge die automatische Zeitsynchronisation beim erneuten Verbinden. Analoge Zeiger können die falsche Zeit anzeigen!</string>
|
||||
<string name="pref_summary_device_spec_settings_title_force_time">Erzwinge automatische Zeitsynchronisation beim erneuten Verbinden. Analoge Uhrzeiger können dabei die falsche Uhrzeit anzeigen!</string>
|
||||
<string name="pref_summary_notifications_and_calls_title_shake_reject">Kopiere Aktion vom Uhr-Knopf</string>
|
||||
<string name="prefs_notifications_and_calls_shake_reject">Schüttelgeste Anruf ignorieren/zurückzuweisen</string>
|
||||
<string name="pref_summary_notifications_and_calls_title_reject">Aus - ignorieren, An - zurückweisen</string>
|
||||
@ -926,15 +926,15 @@
|
||||
<string name="about_activity_title">Über Gadgetbridge</string>
|
||||
<string name="about_title">Über</string>
|
||||
<string name="pref_title_weather_summary">Wird für den LineageOS Wetterdienst genutzt, andere Android-Versionen müssen eine Anwendung wie Weather notification nutzen. Mehr Informationen gibt es im Gadgetbride-Wiki.</string>
|
||||
<string name="menuitem_worldclock">Weltzeit</string>
|
||||
<string name="menuitem_worldclock">Weltzeituhr</string>
|
||||
<string name="error_version_check_extreme_caution">ACHTUNG: Fehler beim Prüfen der Information zur Version! Du solltest nicht fortfahren! Versionsname \"%s\" gefunden</string>
|
||||
<string name="permission_granting_mandatory">Alle Berechtigungen sind notwendig und es kann zu Problemen führen, wenn sie nicht genehmigt werden</string>
|
||||
<string name="menuitem_stress">Stress</string>
|
||||
<string name="menuitem_cycles">Periode</string>
|
||||
<string name="menuitem_cycles">Zyklus-Tracking</string>
|
||||
<string name="error_background_service_reason">Start des Hintergrunddienstes fehlgeschlagen wegen einem Fehler. Fehlermeldung:</string>
|
||||
<string name="error_background_service_reason_truncated">Hintergrunddienst konnte nicht gestartet werden, wegen…</string>
|
||||
<string name="error_background_service">Start des Hintergrunddienstes fehlgeschlagen</string>
|
||||
<string name="menuitem_breathing">Atmung</string>
|
||||
<string name="menuitem_breathing">Atemübungen</string>
|
||||
<string name="devicetype_miband5">Mi Band 5</string>
|
||||
<string name="fw_upgrade_notice_miband5">Du bist dabei, die Firmware %s auf dein Mi Band 5 zu installieren.
|
||||
\n
|
||||
@ -996,12 +996,12 @@
|
||||
<string name="prefs_events_forwarding_fellsleep">Beim Einschlafen</string>
|
||||
<string name="activity_type_yoga">Yoga</string>
|
||||
<string name="activity_type_swimming_openwater">Schwimmen (Freiwasser)</string>
|
||||
<string name="Strokes">Schläge</string>
|
||||
<string name="Strokes">Züge</string>
|
||||
<string name="calories_unit">kcal</string>
|
||||
<string name="activity_type_jump_roping">Seilspringen</string>
|
||||
<string name="activity_type_elliptical_trainer">Crosstrainer</string>
|
||||
<string name="activity_type_indoor_cycling">Indoor Cycling</string>
|
||||
<string name="strokes_second">str/s</string>
|
||||
<string name="activity_type_indoor_cycling">Spinning</string>
|
||||
<string name="strokes_second">Züge/s</string>
|
||||
<string name="swimStyle">Schwimmstil</string>
|
||||
<string name="seconds">sek</string>
|
||||
<string name="minutes_km">min/km</string>
|
||||
@ -1031,7 +1031,7 @@
|
||||
<string name="Elevation">Höhe</string>
|
||||
<string name="bpm">bpm</string>
|
||||
<string name="seconds_km">sek/km</string>
|
||||
<string name="strokes_unit">str</string>
|
||||
<string name="strokes_unit">Züge</string>
|
||||
<string name="flatSeconds">Flach</string>
|
||||
<string name="averageStrokesPerSecond">Durchschnittliche Schläge</string>
|
||||
<string name="averageStrokeDistance">Durchschnittliche Schlag Distanz</string>
|
||||
@ -1079,8 +1079,8 @@
|
||||
<string name="activity_error_no_app_for_png">Um diesen Screenshot zu teilen, installiere eine App, die Bilddateien verarbeiten kann.</string>
|
||||
<string name="activity_prefs_chart_min_session_length">Minimale Aktivitätsdauer (Minuten)</string>
|
||||
<string name="activity_prefs_chart_max_idle_phase_length">Pausenlänge zum Trennen von Aktivitäten (Minuten)</string>
|
||||
<string name="activity_prefs_chart_min_steps_per_minute">Minimale Schritte pro Minute zur Erkennung von Aktivität</string>
|
||||
<string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimale Schritte pro Minute zur Lauferkennung</string>
|
||||
<string name="activity_prefs_chart_min_steps_per_minute">Minimale Schritte pro Minute zur Erkennung einer Aktivität (Gehen)</string>
|
||||
<string name="activity_prefs_chart_min_steps_per_minute_for_run">Minimale Schritte pro Minute zur Erkennung einer Laufaktivität</string>
|
||||
<string name="lefun_prefs_interface_language_title">Sprache der Benutzeroberfläche</string>
|
||||
<string name="lefun_prefs_antilost_summary">Das Band wird vibrieren, wenn dein Telefon die Verbindung zum Band unterbricht</string>
|
||||
<string name="lefun_prefs_antilost_title">Anti-Verlust</string>
|
||||
@ -1125,9 +1125,9 @@
|
||||
<string name="menuitem_stopwatch">Stoppuhr</string>
|
||||
<string name="menuitem_dnd">Nicht stören</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Foto machen</string>
|
||||
<string name="menuitem_mutephone">Telefon stumm schalten</string>
|
||||
<string name="menuitem_findphone">Telefon finden</string>
|
||||
<string name="menuitem_takephoto">Kamera-Fernauslösung</string>
|
||||
<string name="menuitem_mutephone">Telefon stummschalten</string>
|
||||
<string name="menuitem_findphone">Telefon suchen</string>
|
||||
<string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
|
||||
<string name="devicetype_amazfit_gts2">Amazfit GTS 2</string>
|
||||
<string name="movement_intensity">Bewegungsintensität</string>
|
||||
@ -1322,8 +1322,8 @@
|
||||
<string name="watchface_widget_type_2nd_tz">Zweite Zeitzone</string>
|
||||
<string name="watchface_widget_type_calories">Kalorien</string>
|
||||
<string name="watchface_widget_type_battery">Akku</string>
|
||||
<string name="watchface_setting_power_saving_hands">Handbewegung ohne Handgelenk deaktivieren</string>
|
||||
<string name="watchface_setting_power_saving_display">Bildschirmaktualisierungen ohne Handgelenk deaktivieren</string>
|
||||
<string name="watchface_setting_power_saving_hands">Bewegungserkennung deaktivieren wenn Uhr nicht getragen</string>
|
||||
<string name="watchface_setting_power_saving_display">Bildschirmaktualisierungen deaktivieren wenn Uhr nicht getragen</string>
|
||||
<string name="pref_summary_huami_force_new_protocol">Aktivieren, wenn das Gerät nach einem Firmware-Upgrade keine Verbindung mehr herstellt</string>
|
||||
<string name="pref_title_huami_force_new_protocol">Neues Auth-Protokoll</string>
|
||||
<string name="menuitem_flashlight">Taschenlampe</string>
|
||||
@ -1482,4 +1482,13 @@
|
||||
<string name="toast_app_must_be_selected">App muss zum Konfigurieren ausgewählt sein</string>
|
||||
<string name="prefs_pressure_relief">Druckentlastung mit Umgebungsgeräuschen</string>
|
||||
<string name="pressure_relief_summary">Verhindert Druckgefühl in den Ohren, wenn Active Noise Cancelling nicht verwendet wird</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Im Ruhemodes Anzeige beim Anheben aktivieren</string>
|
||||
<string name="watchface_dialog_widget_update_timeout">Aktualisierungszeit in Minuten:</string>
|
||||
<string name="device_card_activity_card_title">Aktivitätsinfo in der Geräteübersicht</string>
|
||||
<string name="device_card_activity_card_title_summary">Auswahl der Aktivitätsinfos in der Geräteübersicht</string>
|
||||
<string name="prefs_activity_in_device_card_title">Aktivitätsinfos in der Geräteübersicht anzeigen</string>
|
||||
<string name="prefs_activity_in_device_card_title_summary">Zeige Schritte, Distanz oder Schlaf in der Geräteübersicht</string>
|
||||
<string name="prefs_activity_in_device_card_distance_title_summary">Strecke basiert auf Schrittzahl und Schrittlänge (siehe Einstellungen - Über Dich)</string>
|
||||
<string name="sony_equalizer_preset_excited">Angeregt</string>
|
||||
<string name="battery_case">Batteriefach</string>
|
||||
</resources>
|
@ -335,10 +335,10 @@
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
<string name="menuitem_settings">Settings</string>
|
||||
<string name="menuitem_compass">Compass</string>
|
||||
<string name="menuitem_timer">Timer</string>
|
||||
<string name="menuitem_timer">Countdown</string>
|
||||
<string name="menuitem_alarm">Alarm</string>
|
||||
<string name="menuitem_weather">Weather</string>
|
||||
<string name="menuitem_activity">Activity</string>
|
||||
<string name="menuitem_activity">Workout History</string>
|
||||
<string name="menuitem_status">Status</string>
|
||||
<string name="reset_index">Reset fetch date</string>
|
||||
<string name="share">Share</string>
|
||||
@ -950,7 +950,7 @@
|
||||
<string name="notification_channel_transfer_name">Data transfer</string>
|
||||
<string name="notification_channel_low_battery_name">Low battery</string>
|
||||
<string name="devicetype_lefun">Lefun</string>
|
||||
<string name="menuitem_cycles">Period</string>
|
||||
<string name="menuitem_cycles">Cycle Tracking</string>
|
||||
<string name="menuitem_stress">Stress</string>
|
||||
<string name="menuitem_dnd">DND</string>
|
||||
<string name="menuitem_stopwatch">Stopwatch</string>
|
||||
@ -1329,7 +1329,7 @@
|
||||
<string name="devicetype_amazfit_x">Amazfit X</string>
|
||||
<string name="devicetype_zepp_e">Zepp E</string>
|
||||
<string name="menuitem_findphone">Find Phone</string>
|
||||
<string name="menuitem_takephoto">Take a photo</string>
|
||||
<string name="menuitem_takephoto">Camera Remote</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="pref_title_middle_button_function_short">Middle Button short</string>
|
||||
<string name="pref_title_upper_button_function_long">Upper Button long</string>
|
||||
@ -1479,4 +1479,15 @@
|
||||
<string name="reminder_every_month">Every month</string>
|
||||
<string name="reminder_no_free_slots_description">The device has no free slots for reminders (total slots: %1$s)</string>
|
||||
<string name="prefs_reserve_reminder_calendar_summary">Number of calendar events that will be synchronized</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Activate display upon lift during Do Not Disturb</string>
|
||||
<string name="maxHR">Max Heartrate</string>
|
||||
<string name="spm">steps/min</string>
|
||||
<string name="minSpeed">Minimum</string>
|
||||
<string name="averageCadence">Average Cadence</string>
|
||||
<string name="averageAltitude">Average</string>
|
||||
<string name="maxCadence">Max Cadence</string>
|
||||
<string name="minCadence">Min Cadence</string>
|
||||
<string name="minHR">Min Heartrate</string>
|
||||
<string name="maxStride">Max Stride</string>
|
||||
<string name="minStride">Min Stride</string>
|
||||
</resources>
|
@ -513,7 +513,7 @@
|
||||
<string name="menuitem_notifications">Notificaciones</string>
|
||||
<string name="menuitem_weather">Tiempo</string>
|
||||
<string name="menuitem_alarm">Alarma</string>
|
||||
<string name="menuitem_timer">Temporizador</string>
|
||||
<string name="menuitem_timer">Cuenta atrás</string>
|
||||
<string name="menuitem_compass">Brújula</string>
|
||||
<string name="menuitem_settings">Ajustes</string>
|
||||
<string name="menuitem_music">Música</string>
|
||||
@ -605,7 +605,7 @@
|
||||
<string name="devicetype_roidmi3">Roidmi 3</string>
|
||||
<string name="devicetype_casiogb6900">Casio GB-6900</string>
|
||||
<string name="menuitem_status">Estado</string>
|
||||
<string name="menuitem_activity">Actividad</string>
|
||||
<string name="menuitem_activity">Historial del entrenamiento</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
<string name="watch9_calibration_hint">Establezca la hora que su dispositivo le está mostrando en este momento.</string>
|
||||
<string name="share_log_warning">Tenga en cuenta que Gadgetbridge registra archivos que pueden contener mucha información personal, incluidos, entre otros, datos de salud, identificadores únicos (como la dirección MAC de un dispositivo), preferencias de música, etc. Considere editar el archivo y eliminar esta información antes de enviar el archivo a un informe público.</string>
|
||||
@ -1118,7 +1118,7 @@
|
||||
<string name="menuitem_stopwatch">Cronómetro</string>
|
||||
<string name="menuitem_dnd">No molestar</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Tomar una foto</string>
|
||||
<string name="menuitem_takephoto">Control remoto de la cámara</string>
|
||||
<string name="menuitem_mutephone">Silenciar teléfono</string>
|
||||
<string name="menuitem_findphone">Encontrar teléfono</string>
|
||||
<string name="menuitem_worldclock">Reloj mundial</string>
|
||||
@ -1128,7 +1128,7 @@
|
||||
<string name="menuitem_spo2">SpO2</string>
|
||||
<string name="menuitem_hr">Ritmo cardíaco</string>
|
||||
<string name="menuitem_pai">PAI</string>
|
||||
<string name="menuitem_cycles">Periodo</string>
|
||||
<string name="menuitem_cycles">Seguimiento del ciclo</string>
|
||||
<string name="menuitem_breathing">Respiración</string>
|
||||
<string name="devicetype_lefun">Lefun</string>
|
||||
<string name="devicetype_sg2">Lemfo SG2</string>
|
||||
@ -1486,4 +1486,15 @@
|
||||
<string name="prefs_reserve_reminder_calendar_summary">Número de eventos del calendario que se sincronizarán</string>
|
||||
<string name="qhybrid_title_on_device_confirmation">Activar la confirmación del emparejamiento en el dispositivo</string>
|
||||
<string name="qhybrid_summary_on_device_confirmation">Las confirmaciones de emparejamiento en el dispositivo pueden resultar molestas. Desactivarlas puede hacer que pierda funcionalidad.</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Activar la pantalla al levantarse durante la función \"No molestar\"</string>
|
||||
<string name="averageAltitude">Media</string>
|
||||
<string name="minSpeed">Mínimo</string>
|
||||
<string name="maxHR">Ritmo cardíaco máximo</string>
|
||||
<string name="minHR">Ritmo cardíaco mínimo</string>
|
||||
<string name="minStride">Zancada mínima</string>
|
||||
<string name="averageCadence">Cadencia media</string>
|
||||
<string name="minCadence">Cadencia mínima</string>
|
||||
<string name="spm">pasos/min</string>
|
||||
<string name="maxStride">Zancada máxima</string>
|
||||
<string name="maxCadence">Cadencia máxima</string>
|
||||
</resources>
|
@ -516,10 +516,10 @@ Temps de sommeil préféré en heures</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="reset_index">Réinitialiser la date de récupération</string>
|
||||
<string name="menuitem_status">État</string>
|
||||
<string name="menuitem_activity">Activité</string>
|
||||
<string name="menuitem_activity">Historique d\'activité</string>
|
||||
<string name="menuitem_weather">Météo</string>
|
||||
<string name="menuitem_alarm">Alarme</string>
|
||||
<string name="menuitem_timer">Minuteur</string>
|
||||
<string name="menuitem_timer">Décompte</string>
|
||||
<string name="menuitem_compass">Boussole</string>
|
||||
<string name="menuitem_settings">Réglages</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -1121,7 +1121,7 @@ Temps de sommeil préféré en heures</string>
|
||||
<string name="menuitem_stopwatch">Étape</string>
|
||||
<string name="menuitem_dnd">NPD</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Prendre une photo</string>
|
||||
<string name="menuitem_takephoto">Appareil photo à distance</string>
|
||||
<string name="menuitem_mutephone">Téléphone muet</string>
|
||||
<string name="menuitem_findphone">Trouver mon téléphone</string>
|
||||
<string name="movement_intensity">Intensité du mouvement</string>
|
||||
@ -1486,4 +1486,14 @@ Temps de sommeil préféré en heures</string>
|
||||
<string name="prefs_reserve_reminder_calendar_summary">Nombre d\'évènements du calendrier qui seront synchronisés</string>
|
||||
<string name="title_activity_reminder_details">Détails du rappel</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Activer l\'écran lors d\'un retournement en mode Ne Pas Déranger</string>
|
||||
<string name="maxHR">Rythme cardiaque max</string>
|
||||
<string name="maxStride">Distance de pas max</string>
|
||||
<string name="minStride">Distance de pas min</string>
|
||||
<string name="minCadence">Cadence min</string>
|
||||
<string name="spm">pas/min</string>
|
||||
<string name="averageAltitude">Moyenne</string>
|
||||
<string name="averageCadence">Cadence moyenne</string>
|
||||
<string name="minSpeed">Minimum</string>
|
||||
<string name="minHR">Rythme cardiaque min</string>
|
||||
<string name="maxCadence">Cadence max</string>
|
||||
</resources>
|
@ -509,10 +509,10 @@
|
||||
<string name="share">שיתוף</string>
|
||||
<string name="reset_index">איפוס מועד הקבלה</string>
|
||||
<string name="menuitem_status">מצב</string>
|
||||
<string name="menuitem_activity">פעילות</string>
|
||||
<string name="menuitem_activity">היסטוריית אימונים</string>
|
||||
<string name="menuitem_weather">מזג אוויר</string>
|
||||
<string name="menuitem_alarm">שעון מעורר</string>
|
||||
<string name="menuitem_timer">קוצב זמן</string>
|
||||
<string name="menuitem_timer">ספירה לאחור</string>
|
||||
<string name="menuitem_compass">מצפן</string>
|
||||
<string name="menuitem_settings">הגדרות</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -931,7 +931,7 @@
|
||||
\n
|
||||
\nהמשך מעבר לנקודה זו הוא על אחריותך!</string>
|
||||
<string name="menuitem_stress">לחץ</string>
|
||||
<string name="menuitem_cycles">משך</string>
|
||||
<string name="menuitem_cycles">מעקב מחזוריות</string>
|
||||
<string name="menuitem_breathing">נשימה</string>
|
||||
<string name="devicetype_miband5">Mi Band 5</string>
|
||||
<string name="ignore_bonded_devices_description">הפעלת אפשרות זו תתעלם ממכשירים שכבר אוגדו/צומדו טרם הסריקה</string>
|
||||
@ -1117,7 +1117,7 @@
|
||||
<string name="menuitem_stopwatch">שעון עצר</string>
|
||||
<string name="menuitem_dnd">לא להפריע</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">צילום תמונה</string>
|
||||
<string name="menuitem_takephoto">שליטה במצלמה</string>
|
||||
<string name="menuitem_mutephone">השתקת הטלפון</string>
|
||||
<string name="menuitem_findphone">איתור הטלפון</string>
|
||||
<string name="movement_intensity">חוזק תנועה</string>
|
||||
@ -1481,4 +1481,15 @@
|
||||
<string name="reminder_every_year">כל שנה</string>
|
||||
<string name="reminder_no_free_slots_title">אין חלונות פנויים</string>
|
||||
<string name="prefs_reserve_reminder_calendar_summary">מספר אירועי לוח השנה שיסונכרנו</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">הפעלת התצוגה עקב הנפה במצב לא להפריע</string>
|
||||
<string name="averageAltitude">ממוצע</string>
|
||||
<string name="minCadence">קצב מזערי</string>
|
||||
<string name="minSpeed">מזערי</string>
|
||||
<string name="maxHR">דופק מרבי</string>
|
||||
<string name="minHR">דופק מזערי</string>
|
||||
<string name="averageCadence">קצב ממוצע</string>
|
||||
<string name="maxCadence">קצב מרבי</string>
|
||||
<string name="spm">צעדים לדקה</string>
|
||||
<string name="maxStride">צעד מרבי</string>
|
||||
<string name="minStride">צעד מזערי</string>
|
||||
</resources>
|
@ -530,10 +530,10 @@
|
||||
<string name="menuitem_shortcut_alipay">Alipay (snelkoppeling)</string>
|
||||
<string name="menuitem_shortcut_weather">Weer (snelkoppeling)</string>
|
||||
<string name="menuitem_status">Status</string>
|
||||
<string name="menuitem_activity">Activiteit</string>
|
||||
<string name="menuitem_activity">Trainingsgeschiedenis</string>
|
||||
<string name="menuitem_weather">Weer</string>
|
||||
<string name="menuitem_alarm">Alarm</string>
|
||||
<string name="menuitem_timer">Timer</string>
|
||||
<string name="menuitem_timer">Aftellen</string>
|
||||
<string name="menuitem_compass">Kompas</string>
|
||||
<string name="menuitem_settings">Instellingen</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -925,7 +925,7 @@
|
||||
<string name="error_version_check_extreme_caution">LET OP: Fout bij het controleren van versie-informatie! Je moet niet doorgaan! Zag versienaam \"%s\"</string>
|
||||
<string name="permission_granting_mandatory">Al deze toestemmingen zijn vereist en er kan instabiliteit ontstaan als ze niet worden verleend</string>
|
||||
<string name="menuitem_stress">Spanning</string>
|
||||
<string name="menuitem_cycles">Cycli</string>
|
||||
<string name="menuitem_cycles">Cyclus volgen</string>
|
||||
<string name="menuitem_breathing">Ademhaling</string>
|
||||
<string name="devicetype_miband5">Mi Band 5</string>
|
||||
<string name="fw_upgrade_notice_miband5">U staat op het punt om de %s-firmware op je Mi Band 5 te installeren.
|
||||
@ -1031,7 +1031,7 @@
|
||||
<string name="menuitem_stopwatch">Stopwatch</string>
|
||||
<string name="menuitem_dnd">Niet storen</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Neem een foto</string>
|
||||
<string name="menuitem_takephoto">Camera-afstandsbediening</string>
|
||||
<string name="menuitem_mutephone">Telefoon dempen</string>
|
||||
<string name="menuitem_findphone">Vind telefoon</string>
|
||||
<string name="menuitem_spo2">SpO2</string>
|
||||
@ -1481,4 +1481,15 @@
|
||||
<string name="reminder_delete_confirm_description">Weet je zeker dat je de herinnering wilt verwijderen\?</string>
|
||||
<string name="reminder_no_free_slots_title">Geen vrije plekken</string>
|
||||
<string name="title_activity_set_reminders">Herinneringen instellen</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Activeer het display bij het optillen tijdens Niet Storen</string>
|
||||
<string name="maxHR">Max hartslag</string>
|
||||
<string name="maxStride">Max paslengte</string>
|
||||
<string name="averageCadence">Gemiddelde cadans</string>
|
||||
<string name="minHR">Min hartslag</string>
|
||||
<string name="minSpeed">Minimum</string>
|
||||
<string name="averageAltitude">Gemiddeld</string>
|
||||
<string name="minStride">Min paslengte</string>
|
||||
<string name="minCadence">Min cadans</string>
|
||||
<string name="spm">stappen/min</string>
|
||||
<string name="maxCadence">Max cadans</string>
|
||||
</resources>
|
@ -282,7 +282,7 @@
|
||||
<string name="pref_title_minimize_priority">Ukryj powiadomienia z Gadgetbridge</string>
|
||||
<string name="pref_summary_minimize_priority_off">Ikona na pasku stanu i powiadomienia będą wyświetlane na zablokowanym ekranie</string>
|
||||
<string name="pref_summary_minimize_priority_on">Ikona na pasku stanu i powiadomienia będą ukrywane na zablokowanym ekranie</string>
|
||||
<string name="pref_summary_notification_filter">Niechciane powiadomienia są wyłączone w tym trybie</string>
|
||||
<string name="pref_summary_notification_filter">Blokuj wszystkie powiadomienia, gdy tryb \"Nie przeszkadzać\" jest włączony w telefonie</string>
|
||||
<string name="pref_title_transliteration">Transliteracja</string>
|
||||
<string name="pref_summary_transliteration">Włącz tę opcję, jeśli twoje urządzenie nie obsługuje czcionki twojego języka</string>
|
||||
<string name="pref_header_privacy">Prywatność</string>
|
||||
@ -576,10 +576,10 @@
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
<string name="devicetype_xwatch">XWatch</string>
|
||||
<string name="menuitem_status">Status</string>
|
||||
<string name="menuitem_activity">Aktywność</string>
|
||||
<string name="menuitem_activity">Historia treningów</string>
|
||||
<string name="menuitem_weather">Pogoda</string>
|
||||
<string name="menuitem_alarm">Budzik</string>
|
||||
<string name="menuitem_timer">Minutnik</string>
|
||||
<string name="menuitem_timer">Odliczanie</string>
|
||||
<string name="menuitem_compass">Kompas</string>
|
||||
<string name="menuitem_settings">Ustawienia</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -941,7 +941,7 @@
|
||||
<string name="require_location_provider">Lokalizacja musi być włączona</string>
|
||||
<string name="permission_granting_mandatory">Wszystkie te uprawnienia są wymagane, a ich brak może spowodować niestabilność</string>
|
||||
<string name="about_additional_device_support">Dodatkowe wsparcie dla urządzeń</string>
|
||||
<string name="menuitem_cycles">Okres</string>
|
||||
<string name="menuitem_cycles">Śledzenie cyklu</string>
|
||||
<string name="menuitem_breathing">Oddychanie</string>
|
||||
<string name="devicetype_pinetime_jf">PineTime (Firmware JF)</string>
|
||||
<string name="activity_summary_detail">Szczegóły aktywności sportowej</string>
|
||||
@ -1123,7 +1123,7 @@
|
||||
<string name="menuitem_stopwatch">Stoper</string>
|
||||
<string name="menuitem_dnd">Nie przeszkadzać</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Zrób zdjęcie</string>
|
||||
<string name="menuitem_takephoto">Zdalna kamera</string>
|
||||
<string name="menuitem_mutephone">Wycisz telefon</string>
|
||||
<string name="menuitem_findphone">Znajdź telefon</string>
|
||||
<string name="activity_list_summary_distance">Dystans</string>
|
||||
@ -1401,4 +1401,15 @@
|
||||
<string name="sony_automatic_power_off_3_hour">3 godziny</string>
|
||||
<string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
|
||||
<string name="sony_automatic_power_off_5_min">5 minut</string>
|
||||
<string name="reminder_date">Data</string>
|
||||
<string name="reminder_time">Czas</string>
|
||||
<string name="reminder_message">Wiadomość</string>
|
||||
<string name="sony_equalizer_preset_speech">Mowa</string>
|
||||
<string name="sony_equalizer_preset_bass_boost">Podbicie basu</string>
|
||||
<string name="sony_surround_mode_club">Klub</string>
|
||||
<string name="sony_equalizer_preset_vocal">Wokal</string>
|
||||
<string name="pref_header_equalizer">Korektor dźwięku</string>
|
||||
<string name="watchface_dialog_widget_timezone">Strefa czasowa:</string>
|
||||
<string name="sony_equalizer_preset_off">Wyłączony</string>
|
||||
<string name="sony_equalizer">Korektor dźwięku</string>
|
||||
</resources>
|
@ -372,7 +372,7 @@
|
||||
<string name="liveactivity_live_activity">Canlı etkinlik</string>
|
||||
<string name="male">Erkek</string>
|
||||
<string name="maximum_duration">Süre</string>
|
||||
<string name="menuitem_activity">Etkinlik</string>
|
||||
<string name="menuitem_activity">Egzersiz Geçmişi</string>
|
||||
<string name="menuitem_alarm">Alarm</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
<string name="menuitem_compass">Pusula</string>
|
||||
@ -384,7 +384,7 @@
|
||||
<string name="menuitem_shortcut_alipay">Alipay (Kısayol)</string>
|
||||
<string name="menuitem_shortcut_weather">Hava Durumu (Kısayol)</string>
|
||||
<string name="menuitem_status">Durum</string>
|
||||
<string name="menuitem_timer">Zamanlama</string>
|
||||
<string name="menuitem_timer">Geri Sayım</string>
|
||||
<string name="menuitem_weather">Hava Durumu</string>
|
||||
<string name="message_cannot_pair_no_mac">MAC adresi verilmedi, eşleştirilemiyor.</string>
|
||||
<string name="mi2_dnd_automatic">Otomatik (uyku algılama)</string>
|
||||
@ -948,7 +948,7 @@
|
||||
<string name="error_background_service_reason">Bir istisna nedeniyle arka plan hizmetini başlatma başarısız oldu. Hata:</string>
|
||||
<string name="error_background_service">Arka plan hizmeti başlatılamadı</string>
|
||||
<string name="menuitem_stress">Stres</string>
|
||||
<string name="menuitem_cycles">Döngü</string>
|
||||
<string name="menuitem_cycles">Döngü İzleme</string>
|
||||
<string name="menuitem_breathing">Nefes Alma</string>
|
||||
<string name="devicetype_miband5">Mi Band 5</string>
|
||||
<string name="fw_upgrade_notice_miband5">Mi Band 5 aygıtınıza %s ürün yazılımını kurmak üzeresiniz.
|
||||
@ -1139,9 +1139,9 @@
|
||||
<string name="find_lost_device_message">%1$s aransın mı\?</string>
|
||||
<string name="devicetype_amazfit_gtr2">Amazfit GTR 2</string>
|
||||
<string name="menuitem_stopwatch">Kronometre</string>
|
||||
<string name="menuitem_dnd">Rahatsız etmeyin</string>
|
||||
<string name="menuitem_dnd">Rahatsız Etme</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Fotoğraf çek</string>
|
||||
<string name="menuitem_takephoto">Kamera Kumandası</string>
|
||||
<string name="menuitem_mutephone">Telefonun Sesini Kapat</string>
|
||||
<string name="menuitem_findphone">Telefonu Bul</string>
|
||||
<string name="movement_intensity">Hareket yoğunluğu</string>
|
||||
@ -1508,4 +1508,14 @@
|
||||
<string name="reminder_time_every_year">%1$s, her yıl</string>
|
||||
<string name="reminder_once">Bir defa</string>
|
||||
<string name="reminder_delete_confirm_description">Hatırlatıcıyı silmek istediğinizden emin misiniz\?</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Rahatsız Etme sırasında kaldırıldığında ekranı etkinleştir</string>
|
||||
<string name="maxHR">En Yüksek Kalp Ritmi</string>
|
||||
<string name="averageAltitude">Ortalama</string>
|
||||
<string name="minSpeed">Asgari</string>
|
||||
<string name="minHR">En Düşük Kalp Ritmi</string>
|
||||
<string name="maxCadence">En Yüksek Tempo</string>
|
||||
<string name="maxStride">En Uzun Adım</string>
|
||||
<string name="minStride">En Kısa Adım</string>
|
||||
<string name="averageCadence">Ortalama Tempo</string>
|
||||
<string name="minCadence">En Düşük Tempo</string>
|
||||
</resources>
|
@ -435,10 +435,10 @@
|
||||
<string name="choose_auto_export_location">Вибрати теку експорту</string>
|
||||
<string name="menuitem_status">Стан</string>
|
||||
<string name="menuitem_notifications">Сповіщення</string>
|
||||
<string name="menuitem_activity">Активність</string>
|
||||
<string name="menuitem_activity">Історія тренувань</string>
|
||||
<string name="menuitem_weather">Погода</string>
|
||||
<string name="menuitem_alarm">Будильник</string>
|
||||
<string name="menuitem_timer">Таймер</string>
|
||||
<string name="menuitem_timer">Відлік</string>
|
||||
<string name="menuitem_compass">Компас</string>
|
||||
<string name="menuitem_settings">Налаштування</string>
|
||||
<string name="menuitem_music">Музика</string>
|
||||
@ -990,7 +990,7 @@
|
||||
<string name="totalStride">Звичайний крок</string>
|
||||
<string name="maxPace">Швидки темп</string>
|
||||
<string name="minPace">Найповільніший темп</string>
|
||||
<string name="maxSpeed">Максимум</string>
|
||||
<string name="maxSpeed">Максимальна</string>
|
||||
<string name="caloriesBurnt">Калорії</string>
|
||||
<string name="activeSeconds">Активно</string>
|
||||
<string name="steps">Кроки</string>
|
||||
@ -1028,7 +1028,7 @@
|
||||
<string name="hr_widget_last_notification">Останнє сповіщення</string>
|
||||
<string name="menuitem_worldclock">Світовий час</string>
|
||||
<string name="menuitem_stress">Стрес</string>
|
||||
<string name="menuitem_cycles">Період</string>
|
||||
<string name="menuitem_cycles">Відстеження циклу</string>
|
||||
<string name="menuitem_breathing">Дихання</string>
|
||||
<string name="devicetype_pinetime_jf">PineTime (Прошивка JF)</string>
|
||||
<string name="devicetype_tlw64">TLW64</string>
|
||||
@ -1115,7 +1115,7 @@
|
||||
<string name="menuitem_stopwatch">Секундомер</string>
|
||||
<string name="menuitem_dnd">Тихий режим</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">Зробити фото</string>
|
||||
<string name="menuitem_takephoto">Віддалене керування камерою</string>
|
||||
<string name="menuitem_mutephone">Вимкнути звук телефону</string>
|
||||
<string name="menuitem_findphone">Знайти телефон</string>
|
||||
<string name="menuitem_spo2">SpO2</string>
|
||||
@ -1488,4 +1488,15 @@
|
||||
<string name="reminder_delete_confirm_title">Видалити нагадування</string>
|
||||
<string name="reminder_no_free_slots_title">Немає вільних комірок</string>
|
||||
<string name="reminder_no_free_slots_description">На пристрої немає вільних комірок для нагадувань (загальна кількість комірок: %1$s)</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">Вмикати дисплей під час підйому у режимі «Не турбувати»</string>
|
||||
<string name="averageAltitude">Середня</string>
|
||||
<string name="minSpeed">Мінімальна</string>
|
||||
<string name="maxHR">Максимальний пульс</string>
|
||||
<string name="spm">кроків/хв</string>
|
||||
<string name="averageCadence">Середній темп</string>
|
||||
<string name="maxCadence">Максимальний темп</string>
|
||||
<string name="minHR">Мінімальний пульс</string>
|
||||
<string name="maxStride">Максимальний крок</string>
|
||||
<string name="minStride">Мінімальний крок</string>
|
||||
<string name="minCadence">Мінімальний темп</string>
|
||||
</resources>
|
@ -586,10 +586,10 @@
|
||||
<string name="menuitem_shortcut_weather">天气(快捷方式)</string>
|
||||
<string name="menuitem_status">状态</string>
|
||||
<string name="menuitem_notifications">通知</string>
|
||||
<string name="menuitem_activity">活动</string>
|
||||
<string name="menuitem_activity">锻炼历史</string>
|
||||
<string name="menuitem_weather">天气</string>
|
||||
<string name="menuitem_alarm">闹钟</string>
|
||||
<string name="menuitem_timer">计时器</string>
|
||||
<string name="menuitem_timer">倒计时</string>
|
||||
<string name="menuitem_compass">指南针</string>
|
||||
<string name="menuitem_settings">设置</string>
|
||||
<string name="menuitem_alipay">支付宝</string>
|
||||
@ -928,7 +928,7 @@
|
||||
<string name="error_background_service">启动后台服务失败</string>
|
||||
<string name="error_version_check_extreme_caution">注意:检查版本信息时发生错误!您不应该继续下去,已知版本名“%s”</string>
|
||||
<string name="menuitem_stress">压力</string>
|
||||
<string name="menuitem_cycles">周期</string>
|
||||
<string name="menuitem_cycles">周期跟踪</string>
|
||||
<string name="menuitem_breathing">呼吸</string>
|
||||
<string name="devicetype_miband5">小米手环5</string>
|
||||
<string name="fw_upgrade_notice_miband5">您即将在您的 小米手环5 上安装 %s 固件。
|
||||
@ -1122,7 +1122,7 @@
|
||||
<string name="menuitem_stopwatch">秒表</string>
|
||||
<string name="menuitem_dnd">勿扰</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_takephoto">拍照</string>
|
||||
<string name="menuitem_takephoto">相机遥控</string>
|
||||
<string name="menuitem_mutephone">静音手机</string>
|
||||
<string name="menuitem_findphone">查找手机</string>
|
||||
<string name="movement_intensity">运动强度</string>
|
||||
@ -1488,4 +1488,14 @@
|
||||
<string name="reminder_no_free_slots_title">没有可用空位</string>
|
||||
<string name="reminder_no_free_slots_description">该设备已经没有可用于提醒的可用的空位(总计空位:%1$s)</string>
|
||||
<string name="mi2_prefs_do_not_disturb_lift_wrist">在请勿打扰期间在抬起时激活显示</string>
|
||||
<string name="averageAltitude">平均</string>
|
||||
<string name="minSpeed">最小</string>
|
||||
<string name="maxStride">最大步幅</string>
|
||||
<string name="averageCadence">平均节奏</string>
|
||||
<string name="maxCadence">最大节奏</string>
|
||||
<string name="minCadence">最小节奏</string>
|
||||
<string name="maxHR">最大心率</string>
|
||||
<string name="minHR">最小心率</string>
|
||||
<string name="minStride">最小步幅</string>
|
||||
<string name="spm">步数/分钟</string>
|
||||
</resources>
|
@ -936,12 +936,12 @@
|
||||
<!-- Menus on the smart device -->
|
||||
<string name="menuitem_status">Status</string>
|
||||
<string name="menuitem_notifications">Notifications</string>
|
||||
<string name="menuitem_activity">Activity</string>
|
||||
<string name="menuitem_activity">Workout History</string>
|
||||
<string name="menuitem_weather">Weather</string>
|
||||
<string name="menuitem_breathing">Breathing</string>
|
||||
<string name="menuitem_cycles">Period</string>
|
||||
<string name="menuitem_cycles">Cycle Tracking</string>
|
||||
<string name="menuitem_alarm">Alarm</string>
|
||||
<string name="menuitem_timer">Timer</string>
|
||||
<string name="menuitem_timer">Countdown</string>
|
||||
<string name="menuitem_compass">Compass</string>
|
||||
<string name="menuitem_settings">Settings</string>
|
||||
<string name="menuitem_alipay">Alipay</string>
|
||||
@ -959,7 +959,7 @@
|
||||
<string name="menuitem_worldclock">World Clock</string>
|
||||
<string name="menuitem_findphone">Find Phone</string>
|
||||
<string name="menuitem_mutephone">Mute Phone</string>
|
||||
<string name="menuitem_takephoto">Take a photo</string>
|
||||
<string name="menuitem_takephoto">Camera Remote</string>
|
||||
<string name="menuitem_alexa">Alexa</string>
|
||||
<string name="menuitem_dnd">DND</string>
|
||||
<string name="menuitem_stopwatch">Stopwatch</string>
|
||||
@ -1317,7 +1317,7 @@
|
||||
<string name="watchface_setting_title_power_saving">Power saving</string>
|
||||
<string name="watchface_setting_power_saving_display">Disable display updates while off wrist</string>
|
||||
<string name="watchface_setting_power_saving_hands">Disable hands movement while off wrist</string>
|
||||
<string name="prefs_sleep_time">Sleep times</string>
|
||||
<string name="prefs_sleep_time">Sleep times</string>
|
||||
<string name="prefs_sleep_time_label">Define sleep hours</string>
|
||||
<string name="prefs_sleep_time_summary">Specifies times when sleep is registered</string>
|
||||
<string name="prefs_vibration_enable">Enable vibrations</string>
|
||||
@ -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>
|
||||
@ -1427,5 +1427,4 @@
|
||||
<string name="watchface_dialog_widget_timeout_show_circle">Show circle on timeout:</string>
|
||||
<string name="qhybrid_title_on_device_confirmation">Enable on-device pairing confirmation</string>
|
||||
<string name="qhybrid_summary_on_device_confirmation">On-device pairing confirmations can get annoying. Disabling them might lose you functionality.</string>
|
||||
|
||||
</resources>
|
||||
</resources>
|
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