Sony WF-SP800N: Initial Support

This commit is contained in:
José Rebelo 2021-12-24 14:20:57 +00:00 committed by Gitea
parent 8eb0b310ba
commit c4874de11a
35 changed files with 619 additions and 11 deletions

View File

@ -79,7 +79,7 @@ vendor's servers.
- PineTime (InfiniTime Firmware)
- Roidmi, Roidmi 3, Mojietu 3 (Bluetooth FM Transmitters)
- [SMA](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/SMA) Q2 (SMA-Q2-OSS Firmware)
- Sony WH-1000XM3
- Sony WH-1000XM3, WF-SP800N
- Teclast H10, H30
- TLW64
- Vibratissimo (Experimental)

View File

@ -127,6 +127,9 @@ public class DeviceSettingsPreferenceConst {
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_PAUSE_WHEN_TAKEN_OFF = "sony_pause_when_taken_off";
public static final String PREF_SONY_BUTTON_MODE_LEFT = "pref_sony_button_mode_left";
public static final String PREF_SONY_BUTTON_MODE_RIGHT = "pref_sony_button_mode_right";
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";

View File

@ -121,6 +121,7 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF;
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_VIBRATION_STRENGH_PERCENTAGE;
@ -142,6 +143,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
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_BUTTON_MODE_RIGHT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT;
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;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD;
@ -560,6 +563,9 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_SONY_EQUALIZER_BASS);
addPreferenceHandlerFor(PREF_SONY_AUDIO_UPSAMPLING);
addPreferenceHandlerFor(PREF_SONY_TOUCH_SENSOR);
addPreferenceHandlerFor(PREF_SONY_PAUSE_WHEN_TAKEN_OFF);
addPreferenceHandlerFor(PREF_SONY_BUTTON_MODE_LEFT);
addPreferenceHandlerFor(PREF_SONY_BUTTON_MODE_RIGHT);
addPreferenceHandlerFor(PREF_SONY_AUTOMATIC_POWER_OFF);
addPreferenceHandlerFor(PREF_SONY_NOTIFICATION_VOICE_GUIDE);

View File

@ -139,7 +139,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
@Override
public void onClick(View v) {
if (device.isInitialized() || device.isConnected()) {
showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress);
} else {
@ -647,6 +647,27 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
});
}
holder.powerOff.setVisibility(View.GONE);
if (device.isInitialized() && coordinator.supportsPowerOff()) {
holder.powerOff.setVisibility(View.VISIBLE);
holder.powerOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new AlertDialog.Builder(context)
.setTitle(R.string.controlcenter_power_off_confirm_title)
.setMessage(R.string.controlcenter_power_off_confirm_description)
.setIcon(R.drawable.ic_power_settings_new)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int whichButton) {
GBApplication.deviceService().onPowerOff();
}
})
.setNegativeButton(android.R.string.no, null)
.show();
}
});
}
//remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener()
@ -789,6 +810,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
LinearLayout fmFrequencyBox;
TextView fmFrequencyLabel;
ImageView ledColor;
ImageView powerOff;
//activity card
LinearLayout cardViewActivityCardLayout;
@ -840,10 +862,11 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
ledColor = view.findViewById(R.id.device_led_color);
powerOff = view.findViewById(R.id.device_action_power_off);
heartRateStatusBox = view.findViewById(R.id.device_heart_rate_status_box);
heartRateStatusLabel = view.findViewById(R.id.heart_rate_status);
heartRateIcon = view.findViewById(R.id.device_heart_rate_status);
cardViewActivityCardLayout = view.findViewById(R.id.card_view_activity_card_layout);
TotalStepsChart = view.findViewById(R.id.activity_dashboard_piechart1);

View File

@ -279,5 +279,8 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return new BatteryConfig[0];
}
@Override
public boolean supportsPowerOff() {
return false;
}
}

View File

@ -378,4 +378,5 @@ public interface DeviceCoordinator {
BatteryConfig[] getBatteryConfig();
boolean supportsPowerOff();
}

View File

@ -118,4 +118,6 @@ public interface EventHandler {
* @param color the new color, in ARGB, with alpha = 255
*/
void onSetLedColor(int color);
void onPowerOff();
}

View File

@ -0,0 +1,76 @@
/* 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.coordinators;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class SonyWFSP800NCoordinator extends SonyHeadphonesCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(final GBDeviceCandidate candidate) {
if (candidate.getName().contains("WF-SP800N")) {
return DeviceType.SONY_WF_SP800N;
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.SONY_WF_SP800N;
}
@Override
public int getBatteryCount() {
return 3;
}
@Override
public boolean supportsPowerOff() {
return true;
}
@Override
public BatteryConfig[] getBatteryConfig() {
final BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
final BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_galaxy_buds_l, R.string.left_earbud);
final BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_galaxy_buds_r, R.string.right_earbud);
return new BatteryConfig[]{battery1, battery2, battery3};
}
@Override
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
return new int[]{
R.xml.devicesettings_sony_headphones_ambient_sound_control,
R.xml.devicesettings_header_other,
R.xml.devicesettings_sony_headphones_equalizer,
R.xml.devicesettings_header_system,
R.xml.devicesettings_sony_headphones_button_modes_left_right,
R.xml.devicesettings_sony_headphones_pause_when_taken_off,
R.xml.devicesettings_sony_wf_sp800n,
R.xml.devicesettings_sony_headphones_notifications_voice_guide
};
}
}

View File

@ -44,7 +44,7 @@ public class SonyWH1000XM3Coordinator extends SonyHeadphonesCoordinator {
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
return new int[]{
R.xml.devicesettings_sony_warning_wh1000xm3,
R.xml.devicesettings_sony_headphones_ambient_sound_control,
R.xml.devicesettings_sony_headphones_ambient_sound_control_wind_noise_reduction,
R.xml.devicesettings_header_other,
R.xml.devicesettings_sony_headphones_equalizer,
R.xml.devicesettings_sony_headphones_sound_position,

View File

@ -0,0 +1,74 @@
/* Copyright (C) 2021 José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <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 ButtonModes {
public enum Mode {
OFF((byte) 0xff),
AMBIENT_SOUND_CONTROL((byte) 0x00),
PLAYBACK_CONTROL((byte) 0x20),
VOLUME_CONTROL((byte) 0x10);
private final byte code;
Mode(final byte code) {
this.code = code;
}
public byte getCode() {
return this.code;
}
}
final Mode left;
final Mode right;
public ButtonModes(final Mode left, final Mode right) {
this.left = left;
this.right = right;
}
public Mode getModeLeft() {
return left;
}
public Mode getModeRight() {
return right;
}
public Map<String, Object> toPreferences() {
return new HashMap<String, Object>() {{
put(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT, left.name().toLowerCase(Locale.getDefault()));
put(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT, right.name().toLowerCase(Locale.getDefault()));
}};
}
public static ButtonModes fromPreferences(final SharedPreferences prefs) {
return new ButtonModes(
Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT, "off").toUpperCase()),
Mode.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT, "off").toUpperCase())
);
}
}

View File

@ -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 PauseWhenTakenOff {
private final boolean enabled;
public PauseWhenTakenOff(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_PAUSE_WHEN_TAKEN_OFF, enabled);
}};
}
public static PauseWhenTakenOff fromPreferences(final SharedPreferences prefs) {
return new PauseWhenTakenOff(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF, false));
}
}

View File

@ -449,4 +449,10 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_LED_COLOR, color);
invokeService(intent);
}
@Override
public void onPowerOff() {
Intent intent = createIntent().setAction(ACTION_POWER_OFF);
invokeService(intent);
}
}

View File

@ -69,6 +69,7 @@ public interface DeviceService extends EventHandler {
String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency";
String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
String ACTION_POWER_OFF = PREFIX + ".action.power_off";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
String EXTRA_NOTIFICATION_ID = "notification_id";

View File

@ -104,6 +104,7 @@ public enum DeviceType {
GALAXY_BUDS_LIVE(419, R.drawable.ic_device_galaxy_buds_live, R.drawable.ic_device_galaxy_buds_live_disabled, R.string.devicetype_galaxybuds_live),
GALAXY_BUDS(420, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_galaxybuds),
SONY_WH_1000XM3(430, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_sony_wh_1000xm3),
SONY_WF_SP800N(431, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_sony_wf_sp800n),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -102,6 +102,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FI
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_POWER_OFF;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_READ_CONFIGURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
@ -632,6 +633,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.onSetLedColor(color);
}
break;
case ACTION_POWER_OFF:
mDeviceSupport.onPowerOff();
break;
case ACTION_SET_FM_FREQUENCY:
float frequency = intent.getFloatExtra(EXTRA_FM_FREQUENCY, -1);
if (frequency != -1) {

View File

@ -373,6 +373,9 @@ public class DeviceSupportFactory {
case SONY_WH_1000XM3:
deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case SONY_WF_SP800N:
deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
}
if (deviceSupport != null) {
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);

View File

@ -413,4 +413,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onSetLedColor(color);
}
@Override
public void onPowerOff() {
if (checkBusy("power off event")) {
return;
}
delegate.onPowerOff();
}
}

View File

@ -22,6 +22,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Intent;
import org.slf4j.Logger;
@ -367,6 +368,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
}
@Override
public void onPowerOff() {
}
@Override
public void onSetReminders(ArrayList<? extends Reminder> reminders) {

View File

@ -57,6 +57,7 @@ public class SonyHeadphonesIoThread extends BtClassicIoThread {
if (initRetries++ < 2) {
LOG.warn("Init retry {}", initRetries);
mProtocol.decreasePendingAcks();
write(mProtocol.encodeInit());
scheduleInitRetry();
} else {

View File

@ -34,8 +34,10 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDevi
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.ButtonModes;
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.PauseWhenTakenOff;
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;
@ -175,6 +177,13 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
case DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF:
configRequest = protocolImpl.setAutomaticPowerOff(AutomaticPowerOff.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT:
case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT:
configRequest = protocolImpl.setButtonModes(ButtonModes.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF:
configRequest = protocolImpl.setPauseWhenTakenOff(PauseWhenTakenOff.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE:
configRequest = protocolImpl.setVoiceNotifications(VoiceNotifications.fromPreferences(prefs));
break;
@ -200,6 +209,15 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
return null;
}
@Override
public byte[] encodePowerOff() {
if (protocolImpl != null) {
return protocolImpl.powerOff().encode(sequenceNumber);
}
return super.encodePowerOff();
}
public byte[] encodeAck(byte sequenceNumber) {
return new Message(MessageType.ACK, (byte) (1 - sequenceNumber), new byte[0]).encode();
}
@ -226,6 +244,10 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
return pendingAcks;
}
public void decreasePendingAcks() {
pendingAcks--;
}
public byte[] getFromQueue() {
return requestQueue.remove().encode(sequenceNumber);
}

View File

@ -22,8 +22,10 @@ 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.ButtonModes;
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.PauseWhenTakenOff;
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;
@ -62,6 +64,14 @@ public abstract class AbstractSonyProtocolImpl {
public abstract Request setAutomaticPowerOff(final AutomaticPowerOff config);
public abstract Request getButtonModes();
public abstract Request setButtonModes(final ButtonModes config);
public abstract Request getPauseWhenTakenOff();
public abstract Request setPauseWhenTakenOff(final PauseWhenTakenOff config);
public abstract Request getEqualizer();
public abstract Request setEqualizerPreset(final EqualizerPreset config);
@ -86,5 +96,7 @@ public abstract class AbstractSonyProtocolImpl {
public abstract Request startNoiseCancellingOptimizer();
public abstract Request powerOff();
public abstract List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload);
}

View File

@ -36,6 +36,8 @@ public enum PayloadType {
AUDIO_CODEC_REPLY(MessageType.COMMAND_1, 0x19),
AUDIO_CODEC_NOTIFY(MessageType.COMMAND_1, 0x1b),
POWER_OFF(MessageType.COMMAND_1, 0x22),
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),

View File

@ -36,8 +36,10 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInf
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.ButtonModes;
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.PauseWhenTakenOff;
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;
@ -210,6 +212,53 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
);
}
public Request getButtonModes() {
return new Request(
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
new byte[]{
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
(byte) 0x06
}
);
}
public Request setButtonModes(final ButtonModes config) {
return new Request(
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
new byte[]{
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
(byte) 0x06,
(byte) 0x02,
config.getModeLeft().getCode(),
config.getModeRight().getCode()
}
);
}
@Override
public Request getPauseWhenTakenOff() {
return new Request(
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
new byte[]{
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
(byte) 0x03
}
);
}
@Override
public Request setPauseWhenTakenOff(final PauseWhenTakenOff config) {
return new Request(
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
new byte[]{
PayloadType.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
(byte) 0x03,
(byte) 0x00,
(byte) (config.isEnabled() ? 0x01 : 0x00)
}
);
}
@Override
public Request getEqualizer() {
return new Request(
@ -362,6 +411,18 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
);
}
@Override
public Request powerOff() {
return new Request(
PayloadType.POWER_OFF.getMessageType(),
new byte[]{
PayloadType.POWER_OFF.getCode(),
(byte) 0x00,
(byte) 0x01
}
);
}
@Override
public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
final PayloadType payloadType = PayloadType.fromCode(messageType, payload[0]);
@ -394,7 +455,7 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
return handleAudioUpsampling(payload);
case AUTOMATIC_POWER_OFF_BUTTON_MODE_RET:
case AUTOMATIC_POWER_OFF_BUTTON_MODE_NOTIFY:
return handleAutomaticPowerOff(payload);
return handleAutomaticPowerOffButtonMode(payload);
case VOICE_NOTIFICATIONS_RET:
case VOICE_NOTIFICATIONS_NOTIFY:
return handleVoiceNotifications(payload);
@ -433,6 +494,18 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
capabilityRequests.add(getSoundPosition());
capabilityRequests.add(getEqualizer());
break;
case SONY_WF_SP800N:
capabilityRequests.add(getFirmwareVersion());
capabilityRequests.add(getBattery(BatteryType.DUAL));
capabilityRequests.add(getBattery(BatteryType.CASE));
capabilityRequests.add(getAudioCodec());
capabilityRequests.add(getAmbientSoundControl());
capabilityRequests.add(getVoiceNotifications());
capabilityRequests.add(getAutomaticPowerOff());
capabilityRequests.add(getEqualizer());
capabilityRequests.add(getButtonModes());
capabilityRequests.add(getPauseWhenTakenOff());
break;
default:
LOG.error("Unsupported Sony device type '{}' with key '{}'", deviceType, deviceType.getKey());
return null;
@ -569,6 +642,69 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
return Collections.singletonList(event);
}
public List<? extends GBDeviceEvent> handleButtonModes(final byte[] payload) {
if (payload.length != 5) {
LOG.warn("Unexpected payload length {}", payload.length);
return Collections.emptyList();
}
ButtonModes.Mode modeLeft = null;
for (ButtonModes.Mode value : ButtonModes.Mode.values()) {
if (value.getCode() == payload[3]) {
modeLeft = value;
break;
}
}
ButtonModes.Mode modeRight = null;
for (ButtonModes.Mode value : ButtonModes.Mode.values()) {
if (value.getCode() == payload[4]) {
modeRight = value;
break;
}
}
if (modeLeft == null || modeRight == null) {
LOG.warn("Unknown button mode codes {}", String.format("%02x %02x", payload[3], payload[4]));
return Collections.emptyList();
}
LOG.debug("Button Modes: L: {}, R: {}", modeLeft, modeRight);
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
.withPreferences(new ButtonModes(modeLeft, modeRight).toPreferences());
return Collections.singletonList(event);
}
public List<? extends GBDeviceEvent> handlePauseWhenTakenOff(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 pause when taken off code {}", String.format("%02x", payload[3]));
return Collections.emptyList();
}
LOG.debug("Touch Sensor: {}", enabled);
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
.withPreferences(new PauseWhenTakenOff(enabled).toPreferences());
return Collections.singletonList(event);
}
public List<? extends GBDeviceEvent> handleBattery(final byte[] payload) {
final BatteryType batteryType = BatteryType.fromCode(payload[1]);
@ -585,8 +721,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
final GBDeviceEventBatteryInfo singleBatteryInfo = new GBDeviceEventBatteryInfo();
singleBatteryInfo.batteryIndex = 0;
singleBatteryInfo.state = BatteryState.BATTERY_NORMAL;
singleBatteryInfo.level = payload[2];
singleBatteryInfo.state = payload[3] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
batteryEvents.add(singleBatteryInfo);
} else if (BatteryType.DUAL.equals(batteryType)) {
@ -597,8 +733,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoLeft = new GBDeviceEventBatteryInfo();
gbDeviceEventBatteryInfoLeft.batteryIndex = 1;
gbDeviceEventBatteryInfoLeft.state = BatteryState.BATTERY_NORMAL;
gbDeviceEventBatteryInfoLeft.level = payload[2];
gbDeviceEventBatteryInfoLeft.state = payload[3] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
batteryEvents.add(gbDeviceEventBatteryInfoLeft);
}
@ -607,8 +743,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
final GBDeviceEventBatteryInfo gbDeviceEventBatteryInfoRight = new GBDeviceEventBatteryInfo();
gbDeviceEventBatteryInfoRight.batteryIndex = 2;
gbDeviceEventBatteryInfoRight.state = BatteryState.BATTERY_NORMAL;
gbDeviceEventBatteryInfoRight.level = payload[4];
gbDeviceEventBatteryInfoRight.state = payload[5] == 1 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL;
batteryEvents.add(gbDeviceEventBatteryInfoRight);
}
@ -714,6 +850,19 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
return Collections.emptyList();
}
public List<? extends GBDeviceEvent> handleAutomaticPowerOffButtonMode(final byte[] payload) {
switch (payload[1]) {
case 0x04:
return handleAutomaticPowerOff(payload);
case 0x03:
return handlePauseWhenTakenOff(payload);
case 0x06:
return handleButtonModes(payload);
}
return Collections.emptyList();
}
public List<? extends GBDeviceEvent> handleVirtualSound(final byte[] payload) {
if (payload.length != 3) {
LOG.warn("Unexpected payload length {}", payload.length);
@ -856,6 +1005,8 @@ public class SonyProtocolImplV1 extends AbstractSonyProtocolImpl {
switch (deviceType) {
case SONY_WH_1000XM3:
return true;
case SONY_WF_SP800N:
return false;
default:
LOG.error("Unknown Sony device type '{}' with key '{}'", deviceType, deviceType.getKey());
return false;

View File

@ -265,6 +265,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
sendToDevice(bytes);
}
@Override
public void onPowerOff() {
byte[] bytes = gbDeviceProtocol.encodePowerOff();
sendToDevice(bytes);
}
@Override
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);

View File

@ -143,6 +143,10 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodePowerOff() {
return null;
}
public byte[] encodeReminders(ArrayList<? extends Reminder> reminders) {
return null;
}

View File

@ -113,6 +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.coordinators.SonyWFSP800NCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
@ -315,6 +316,7 @@ public class DeviceHelper {
result.add(new GalaxyBudsDeviceCoordinator());
result.add(new GalaxyBudsLiveDeviceCoordinator());
result.add(new SonyWH1000XM3Coordinator());
result.add(new SonyWFSP800NCoordinator());
return result;
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
</vector>

View File

@ -477,6 +477,20 @@
</LinearLayout>
<ImageView
android:id="@+id/device_action_power_off"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="3dp"
android:layout_toEndOf="@id/device_heart_rate_status_box"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_power_off"
android:focusable="true"
android:padding="3dp"
android:scaleType="fitXY"
card_view:srcCompat="@drawable/ic_power_settings_new"
card_view:tint="@color/secondarytext" />
</com.google.android.flexbox.FlexboxLayout>

View File

@ -1235,6 +1235,10 @@
<string name="sony_automatic_power_off_1_hour">1 hora</string>
<string name="sony_automatic_power_off_3_hour">3 horas</string>
<string name="sony_automatic_power_off_when_taken_off">Quando retirar os auscultadores</string>
<string name="sony_button_mode_off">Desligado</string>
<string name="sony_button_mode_ambient_sound_control">Controlo Som Ambiente</string>
<string name="sony_button_mode_playback_control">Controlo da reprodução</string>
<string name="sony_button_mode_volume_control">Controlo do Volume</string>
<string name="uncheck_all_applications">Desmarcar todos as aplicações</string>
<string name="fw_upgrade_notice_amazfitx">Está prestes a instalar o firmware %s no seu Amazfit X.
\n

View File

@ -1949,20 +1949,32 @@
<item>3</item>
</string-array>
<string-array name="sony_ambient_sound_control_names">
<string-array name="sony_ambient_sound_control_wind_noise_reduction_names">
<item>@string/sony_ambient_sound_off</item>
<item>@string/sony_ambient_sound_noise_cancelling</item>
<item>@string/sony_ambient_sound_wind_noise_reduction</item>
<item>@string/sony_ambient_sound_ambient_sound</item>
</string-array>
<string-array name="sony_ambient_sound_control_values">
<string-array name="sony_ambient_sound_control_wind_noise_reduction_values">
<item>off</item>
<item>noise_cancelling</item>
<item>wind_noise_reduction</item>
<item>ambient_sound</item>
</string-array>
<string-array name="sony_ambient_sound_control_names">
<item>@string/sony_ambient_sound_off</item>
<item>@string/sony_ambient_sound_noise_cancelling</item>
<item>@string/sony_ambient_sound_ambient_sound</item>
</string-array>
<string-array name="sony_ambient_sound_control_values">
<item>off</item>
<item>noise_cancelling</item>
<item>ambient_sound</item>
</string-array>
<string-array name="sony_sound_position_names">
<item>@string/sony_sound_position_off</item>
<item>@string/sony_sound_position_front</item>
@ -2042,5 +2054,29 @@
<item>after_1_hour</item>
<item>after_3_hour</item>
</string-array>
<string-array name="sony_wfsp800n_automatic_power_off_names">
<item>@string/sony_automatic_power_off_off</item>
<item>@string/sony_automatic_power_off_when_taken_off</item>
</string-array>
<string-array name="sony_wfsp800n_automatic_power_off_values">
<item>off</item>
<item>when_taken_off</item>
</string-array>
<string-array name="sony_wfsp800n_button_mode_names">
<item>@string/sony_button_mode_off</item>
<item>@string/sony_button_mode_ambient_sound_control</item>
<item>@string/sony_button_mode_playback_control</item>
<item>@string/sony_button_mode_volume_control</item>
</string-array>
<string-array name="sony_wfsp800n_button_mode_values">
<item>off</item>
<item>ambient_sound_control</item>
<item>playback_control</item>
<item>volume_control</item>
</string-array>
</resources>

View File

@ -10,6 +10,9 @@
<string name="controlcenter_find_device">Find lost device</string>
<string name="find_lost_device_message">Search for %1$s?</string>
<string name="controlcenter_take_screenshot">Take Screenshot</string>
<string name="controlcenter_power_off">Power Off</string>
<string name="controlcenter_power_off_confirm_title">Power Off</string>
<string name="controlcenter_power_off_confirm_description">Are you sure you want to power off the device?</string>
<string name="controlcenter_change_led_color">Change LED Color</string>
<string name="controlcenter_change_fm_frequency">Change FM Frequency</string>
<string name="controlcenter_connect">Connect…</string>
@ -926,6 +929,7 @@
<string name="devicetype_fitpro">FitPro</string>
<string name="devicetype_domyos_t540"/>
<string name="devicetype_sony_wh_1000xm3">Sony WH-1000XM3</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="choose_auto_export_location">Choose export location</string>
<string name="notification_channel_name">General</string>
<string name="notification_channel_high_priority_name">High-priority</string>
@ -1424,6 +1428,13 @@
<string name="sony_automatic_power_off_1_hour">1 hour</string>
<string name="sony_automatic_power_off_3_hour">3 hours</string>
<string name="sony_automatic_power_off_when_taken_off">When taken off</string>
<string name="sony_pause_when_taken_off">Pause when headphones are taken off</string>
<string name="sony_button_mode_left">Button Mode (Left)</string>
<string name="sony_button_mode_right">Button Mode (Right)</string>
<string name="sony_button_mode_off">Off</string>
<string name="sony_button_mode_ambient_sound_control">Ambient Sound Control</string>
<string name="sony_button_mode_playback_control">Playback Control</string>
<string name="sony_button_mode_volume_control">Volume Control</string>
<string name="watchface_widget_type_custom">Custom widget</string>
<string name="watchface_dialog_widget_timezone">Time zone:</string>
<string name="watchface_dialog_widget_update_timeout">Update timeout in minutes:</string>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_key_header_sony_ambient_sound_control"
android:title="@string/pref_header_sony_ambient_sound_control">
<ListPreference
android:defaultValue="noise_cancelling"
android:entries="@array/sony_ambient_sound_control_wind_noise_reduction_names"
android:entryValues="@array/sony_ambient_sound_control_wind_noise_reduction_values"
android:icon="@drawable/ic_hearing"
android:key="pref_sony_ambient_sound_control"
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"
android:key="pref_sony_ambient_sound_level"
android:max="19"
android:title="@string/sony_ambient_sound_level" />
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_voice"
android:key="pref_sony_focus_voice"
android:title="@string/sony_ambient_sound_focus_voice" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,20 @@
<?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_wfsp800n_button_mode_names"
android:entryValues="@array/sony_wfsp800n_button_mode_values"
android:icon="@drawable/ic_touch"
android:key="pref_sony_button_mode_left"
android:summary="%s"
android:title="@string/sony_button_mode_left" />
<ListPreference
android:defaultValue="off"
android:entries="@array/sony_wfsp800n_button_mode_names"
android:entryValues="@array/sony_wfsp800n_button_mode_values"
android:icon="@drawable/ic_touch"
android:key="pref_sony_button_mode_right"
android:summary="%s"
android:title="@string/sony_button_mode_right" />
</androidx.preference.PreferenceScreen>

View File

@ -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_pause"
android:key="pref_sony_pause_when_taken_off"
android:title="@string/sony_pause_when_taken_off" />
</androidx.preference.PreferenceScreen>

View 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_wfsp800n_automatic_power_off_names"
android:entryValues="@array/sony_wfsp800n_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>