1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-09 03:37:03 +01:00

Add support for Soundcore Motion 300

This commit is contained in:
Severin von Wnuck-Lipinski 2024-08-07 21:38:31 +02:00 committed by José Rebelo
parent 1885b05168
commit be3f8375f9
13 changed files with 1321 additions and 0 deletions

View File

@ -372,6 +372,33 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_SOUNDCORE_CONTROL_DOUBLE_TAP_ACTION_RIGHT = "pref_soundcore_control_double_tap_action_right";
public static final String PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT = "pref_soundcore_control_triple_tap_action_right";
public static final String PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT = "pref_soundcore_control_long_press_action_right";
public static final String PREF_SOUNDCORE_VOICE_PROMPTS = "pref_soundcore_voice_prompts";
public static final String PREF_SOUNDCORE_BUTTON_BRIGHTNESS = "pref_soundcore_button_brightness";
public static final String PREF_SOUNDCORE_AUTO_POWER_OFF = "pref_soundcore_auto_power_off";
public static final String PREF_SOUNDCORE_LDAC_MODE = "pref_soundcore_ldac_mode";
public static final String PREF_SOUNDCORE_ADAPTIVE_DIRECTION = "pref_soundcore_adaptive_direction";
public static final String PREF_SOUNDCORE_EQUALIZER_PRESET = "pref_soundcore_equalizer_preset";
public static final String PREF_SOUNDCORE_EQUALIZER_CUSTOM = "pref_soundcore_equalizer_custom";
public static final String PREF_SOUNDCORE_EQUALIZER_DIRECTION = "pref_soundcore_equalizer_direction";
public static final String PREF_SOUNDCORE_EQUALIZER_RESET = "pref_soundcore_equalizer_reset";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ = "pref_soundcore_equalizer_band1_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE = "pref_soundcore_equalizer_band1_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ = "pref_soundcore_equalizer_band2_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE = "pref_soundcore_equalizer_band2_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ = "pref_soundcore_equalizer_band3_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE = "pref_soundcore_equalizer_band3_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ = "pref_soundcore_equalizer_band4_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE = "pref_soundcore_equalizer_band4_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ = "pref_soundcore_equalizer_band5_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE = "pref_soundcore_equalizer_band5_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ = "pref_soundcore_equalizer_band6_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE = "pref_soundcore_equalizer_band6_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ = "pref_soundcore_equalizer_band7_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE = "pref_soundcore_equalizer_band7_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ = "pref_soundcore_equalizer_band8_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE = "pref_soundcore_equalizer_band8_value";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ = "pref_soundcore_equalizer_band9_freq";
public static final String PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE = "pref_soundcore_equalizer_band9_value";
public static final String PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE = "pref_sony_ambient_sound_control_button_mode";
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";

View File

@ -715,6 +715,31 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_TRIPLE_TAP_ACTION_RIGHT);
addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_LEFT);
addPreferenceHandlerFor(PREF_SOUNDCORE_CONTROL_LONG_PRESS_ACTION_RIGHT);
addPreferenceHandlerFor(PREF_SOUNDCORE_VOICE_PROMPTS);
addPreferenceHandlerFor(PREF_SOUNDCORE_BUTTON_BRIGHTNESS);
addPreferenceHandlerFor(PREF_SOUNDCORE_AUTO_POWER_OFF);
addPreferenceHandlerFor(PREF_SOUNDCORE_LDAC_MODE);
addPreferenceHandlerFor(PREF_SOUNDCORE_ADAPTIVE_DIRECTION);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_PRESET);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_DIRECTION);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ);
addPreferenceHandlerFor(PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE);
addPreferenceHandlerFor(PREF_MOONDROP_EQUALIZER_PRESET);
addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_PLAY_PAUSE_EARBUD);

View File

@ -0,0 +1,93 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300;
import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300.SoundcoreMotion300DeviceSupport;
public class SoundcoreMotion300Coordinator extends AbstractBLClassicDeviceCoordinator {
@Override
public String getManufacturer() {
return "Anker";
}
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("soundcore Motion 300");
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_soundcore_motion300;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new SoundcoreMotion300SettingsCustomizer();
}
@Override
public int getBatteryCount() {
return 1;
}
@Override
public boolean supportsPowerOff() {
return true;
}
@Override
protected void deleteDevice(
@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session)
throws GBException {}
@Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings settings = new DeviceSpecificSettings();
settings.addRootScreen(R.xml.devicesettings_soundcore_motion300);
settings.addRootScreen(DeviceSpecificSettingsScreen.AUDIO);
settings.addSubScreen(
DeviceSpecificSettingsScreen.AUDIO,
R.xml.devicesettings_soundcore_motion300_audio);
settings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
settings.addSubScreen(
DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS,
R.xml.devicesettings_headphones);
return settings;
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return SoundcoreMotion300DeviceSupport.class;
}
}

View File

@ -0,0 +1,113 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300;
import android.os.Parcel;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.SeekBarPreference;
import java.util.Collections;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300.SoundcoreMotion300Protocol;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
public class SoundcoreMotion300SettingsCustomizer implements DeviceSpecificSettingsCustomizer {
public static final Creator<SoundcoreMotion300SettingsCustomizer> CREATOR = new Creator<SoundcoreMotion300SettingsCustomizer>() {
@Override
public SoundcoreMotion300SettingsCustomizer createFromParcel(final Parcel in) {
return new SoundcoreMotion300SettingsCustomizer();
}
@Override
public SoundcoreMotion300SettingsCustomizer[] newArray(final int size) {
return new SoundcoreMotion300SettingsCustomizer[size];
}
};
@Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
if (!preference.getKey().equals(PREF_SOUNDCORE_EQUALIZER_PRESET))
return;
String preset = ((ListPreference)preference).getEntry().toString();
Preference pref = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_CUSTOM);
boolean customEnabled = preset.equals(handler.getContext().getString(R.string.custom));
if (pref != null)
pref.setEnabled(customEnabled);
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
ListPreference equalizerDirection = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_DIRECTION);
if (equalizerDirection != null) {
boolean enabled = prefs.getBoolean(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, true);
equalizerDirection.setVisible(enabled);
}
Preference equalizerReset = handler.findPreference(PREF_SOUNDCORE_EQUALIZER_RESET);
if (equalizerReset != null)
equalizerReset.setOnPreferenceClickListener(pref -> resetEqualizer(handler));
}
private boolean resetEqualizer(final DeviceSpecificSettingsHandler handler) {
// Reset all bands to default settings
for (int i = 0; i < SoundcoreMotion300Protocol.EQUALIZER_PREFS_FREQ.length; i++) {
ListPreference prefFreq = handler.findPreference(SoundcoreMotion300Protocol.EQUALIZER_PREFS_FREQ[i]);
SeekBarPreference prefValue = handler.findPreference(SoundcoreMotion300Protocol.EQUALIZER_PREFS_VALUE[i]);
// Neutral configuration
prefValue.setValue(60);
// Default configuration: 80 Hz, 150 Hz, 300 Hz, 600 Hz, 1.2 kHz, 2.5 kHz, 5 kHz, 9 kHz, 13 kHz
if (i < 7)
prefFreq.setValue("7");
else if (i == 7)
prefFreq.setValue("6");
else if (i == 8)
prefFreq.setValue("1");
}
// Send updated equalizer configuration
handler.notifyPreferenceChanged(PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ);
return true;
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {}
}

View File

@ -217,6 +217,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators
import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.soundcore.liberty3_pro.SoundcoreLiberty3ProCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.soundcore.motion300.SoundcoreMotion300Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
@ -436,6 +437,7 @@ public enum DeviceType {
SONY_WH_1000XM5(SonyWH1000XM5Coordinator.class),
SONY_WF_1000XM5(SonyWF1000XM5Coordinator.class),
SOUNDCORE_LIBERTY3_PRO(SoundcoreLiberty3ProCoordinator.class),
SOUNDCORE_MOTION300(SoundcoreMotion300Coordinator.class),
MOONDROP_SPACE_TRAVEL(MoondropSpaceTravelCoordinator.class),
BOSE_QC35(QC35Coordinator.class),
HONORBAND3(HonorBand3Coordinator.class),

View File

@ -0,0 +1,112 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
public class SoundcorePacket {
private static final Logger LOG = LoggerFactory.getLogger(SoundcorePacket.class);
private static final int HEADER_LENGTH = 10;
private static final short START_OF_PACKET_HOST = (short)0xee08;
private static final short START_OF_PACKET_DEVICE = (short)0xff09;
private static final byte DIRECTION_HOST = (byte)0x00;
private static final byte DIRECTION_DEVICE = (byte)0x01;
private short command;
private byte[] payload;
public SoundcorePacket(short command) {
this(command, new byte[] {});
}
public SoundcorePacket(short command, byte[] payload) {
LOG.debug("Packet: command={}", String.format("0x%04x", command));
this.command = command;
this.payload = payload;
}
public short getCommand() {
return command;
}
public byte[] getPayload() {
return payload;
}
public static SoundcorePacket decode(ByteBuffer buf) {
if (buf.remaining() < HEADER_LENGTH)
return null;
buf.order(ByteOrder.LITTLE_ENDIAN);
if (buf.getShort() != START_OF_PACKET_DEVICE) {
LOG.error("Invalid start of packet: {}", hexdump(buf.array()));
return null;
}
// Skip two zero bytes
buf.getShort();
if (buf.get() != DIRECTION_DEVICE) {
LOG.error("Invalid direction: {}", hexdump(buf.array()));
return null;
}
short command = buf.getShort();
short length = buf.getShort();
if (length < HEADER_LENGTH) {
LOG.error("Invalid length: {}", hexdump(buf.array()));
return null;
}
// Skip checksum byte at the end
byte[] payload = new byte[length - HEADER_LENGTH];
buf.get(payload);
return new SoundcorePacket(command, payload);
}
public byte[] encode() {
ByteBuffer buf = ByteBuffer.allocate(HEADER_LENGTH + payload.length);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putShort(START_OF_PACKET_HOST);
buf.putShort((short)0x0000);
buf.put(DIRECTION_HOST);
buf.putShort(command);
buf.putShort((short)(HEADER_LENGTH + payload.length));
buf.put(payload);
byte checksum = 0;
for (byte val : buf.array())
checksum += val;
buf.put(checksum);
return buf.array();
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class SoundcoreMotion300DeviceSupport extends AbstractHeadphoneDeviceSupport {
@Override
public boolean useAutoConnect() {
return false;
}
@Override
protected GBDeviceProtocol createDeviceProtocol() {
return new SoundcoreMotion300Protocol(getDevice());
}
@Override
protected GBDeviceIoThread createDeviceIOThread() {
return new SoundcoreMotion300IOThread(
getDevice(),
getContext(),
(SoundcoreMotion300Protocol)getDeviceProtocol(),
SoundcoreMotion300DeviceSupport.this,
getBluetoothAdapter());
}
}

View File

@ -0,0 +1,75 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Handler;
import android.os.ParcelUuid;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
public class SoundcoreMotion300IOThread extends BtClassicIoThread {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreMotion300IOThread.class);
private final SoundcoreMotion300Protocol protocol;
private final Handler handler = new Handler();
public SoundcoreMotion300IOThread(
GBDevice device,
Context context,
SoundcoreMotion300Protocol protocol,
SoundcoreMotion300DeviceSupport support,
BluetoothAdapter adapter) {
super(device, context, protocol, support, adapter);
this.protocol = protocol;
}
@Override
protected void initialize() {
setUpdateState(GBDevice.State.INITIALIZING);
// Device requires a little delay to respond to commands
handler.postDelayed(() -> write(protocol.encodeGetDeviceInfo()), 500);
}
@NonNull
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
return UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3135");
}
@Override
protected byte[] parseIncoming(InputStream stream) throws IOException {
byte[] buffer = new byte[1048576];
int bytes = stream.read(buffer);
LOG.debug("read " + bytes + " bytes. " + hexdump(buffer, 0, bytes));
return Arrays.copyOf(buffer, bytes);
}
}

View File

@ -0,0 +1,385 @@
/* Copyright (C) 2024 Severin von Wnuck-Lipinski
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.motion300;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
public class SoundcoreMotion300Protocol extends GBDeviceProtocol {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreMotion300Protocol.class);
// Some of these commands are not used right now, they serve as documentation
private static final short CMD_GET_DEVICE_INFO = (short)0x0101;
private static final short CMD_GET_LDAC_MODE = (short)0x7f01;
private static final short CMD_GET_BUTTON_BRIGHTNESS = (short)0x9310;
private static final short CMD_GET_EQUALIZER = (short)0x8902;
private static final short CMD_GET_CURRENT_DIRECTION = (short)0x8c02;
private static final short CMD_SET_VOICE_PROMPTS = (short)0x9001;
private static final short CMD_SET_BUTTON_BRIGHTNESS = (short)0x9210;
private static final short CMD_SET_AUTO_POWER_OFF = (short)0x8601;
private static final short CMD_SET_LDAC_MODE = (short)0xff01;
private static final short CMD_SET_ADAPTIVE_DIRECTION = (short)0x8a02;
private static final short CMD_SET_EQUALIZER_PRESET = (short)0x8b02;
private static final short CMD_SET_EQUALIZER_CUSTOM = (short)0x8d02;
private static final short CMD_NOTIFY_BATTERY_INFO = (short)0x0301;
private static final short CMD_NOTIFY_CHARGING_INFO = (short)0x0401;
private static final short CMD_NOTIFY_VOLUME_INFO = (short)0x0901;
private static final short CMD_NOTIFY_PLAYBACK_INFO = (short)0x2101;
private static final short CMD_NOTIFY_BASS_MODE = (short)0x8e02;
private static final short CMD_POWER_OFF = (short)0x8901;
public static final String[] EQUALIZER_PREFS_FREQ = new String[] {
PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ,
PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ
};
public static final String[] EQUALIZER_PREFS_VALUE = new String[] {
PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE,
PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE
};
private final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
protected SoundcoreMotion300Protocol(GBDevice device) {
super(device);
}
@Override
public GBDeviceEvent[] decodeResponse(byte[] data) {
ByteBuffer buf = ByteBuffer.wrap(data);
SoundcorePacket packet = SoundcorePacket.decode(buf);
if (packet == null)
return null;
short cmd = packet.getCommand();
byte[] payload = packet.getPayload();
switch (cmd) {
case CMD_GET_DEVICE_INFO:
return handlePacketDeviceInfo(payload);
case CMD_GET_LDAC_MODE:
return handlePacketLdacMode(payload);
case CMD_GET_BUTTON_BRIGHTNESS:
return handlePacketButtonBrightness(payload);
case CMD_GET_EQUALIZER:
return handlePacketEqualizer(payload);
case CMD_SET_ADAPTIVE_DIRECTION:
return handlePacketAdaptiveDirection(payload);
case CMD_NOTIFY_BATTERY_INFO:
return handlePacketBatteryInfo(payload);
case CMD_NOTIFY_CHARGING_INFO:
return handlePacketChargingInfo(payload);
}
return null;
}
private GBDeviceEvent[] handlePacketDeviceInfo(byte[] payload) {
if (payload.length != 29)
return null;
ByteBuffer buf = ByteBuffer.wrap(payload);
byte volume = buf.get();
byte batteryLevel = buf.get();
byte batteryCharging = buf.get();
byte currentlyPlaying = buf.get();
byte voicePrompts = buf.get();
byte autoPowerOffEnabled = buf.get();
byte autoPowerOffDuration = buf.get();
byte[] firmwareBytes = new byte[5];
byte[] serialBytes = new byte[17];
buf.get(firmwareBytes);
buf.get(serialBytes);
String fwVersion = new String(firmwareBytes, StandardCharsets.UTF_8);
String serialNumber = new String(serialBytes, StandardCharsets.UTF_8);
GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
versionInfo.fwVersion = fwVersion;
batteryInfo.state = batteryCharging == (byte)0x00 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_CHARGING;
batteryInfo.level = batteryLevel * 20;
LOG.debug(
"Device info: volume={}, battery={}, charging={}, playing={}, prompts={}, autoOff={}, autoOffDuration={}",
volume, batteryLevel, batteryCharging, currentlyPlaying, voicePrompts, autoPowerOffEnabled, autoPowerOffDuration
);
return new GBDeviceEvent[] {
versionInfo,
batteryInfo,
new GBDeviceEventUpdateDeviceInfo("SERIAL: ", serialNumber),
new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_VOICE_PROMPTS, voicePrompts != (byte)0x00),
new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_AUTO_POWER_OFF, String.valueOf(autoPowerOffDuration + 1)),
new GBDeviceEventSendBytes(encodeRequest(CMD_GET_LDAC_MODE)),
};
}
private GBDeviceEvent[] handlePacketLdacMode(byte[] payload) {
if (payload.length != 1)
return null;
return new GBDeviceEvent[] {
new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_LDAC_MODE, payload[0] != (byte)0x00),
new GBDeviceEventSendBytes(encodeRequest(CMD_GET_BUTTON_BRIGHTNESS)),
};
}
private GBDeviceEvent[] handlePacketButtonBrightness(byte[] payload) {
if (payload.length != 1)
return null;
return new GBDeviceEvent[] {
new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_BUTTON_BRIGHTNESS, String.valueOf(payload[0])),
new GBDeviceEventSendBytes(encodeRequest(CMD_GET_EQUALIZER)),
};
}
private GBDeviceEvent[] handlePacketEqualizer(byte[] payload) {
if (payload.length != 57)
return null;
// Get direction chosen in custom equalizer preferences
Prefs prefs = getDevicePrefs();
int equalizerDirection = Integer.parseInt(prefs.getString(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"));
// Equalizer preset might be larger than 127 -> convert to unsigned int
ByteBuffer buf = ByteBuffer.wrap(payload);
byte adaptiveDirection = buf.get();
byte currentDirection = buf.get();
int equalizerPreset = Byte.toUnsignedInt(buf.get());
byte[] equalizer = new byte[18];
// Choose one of the three custom equalizer configurations
buf.position(buf.position() + equalizerDirection * equalizer.length);
buf.get(equalizer);
LOG.debug(
"Equalizer: adaptiveDirection={}, direction={}, preset={}",
adaptiveDirection, currentDirection, equalizerPreset
);
Map<String, Object> newPrefs = equalizerToPrefs(equalizer);
newPrefs.put(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, adaptiveDirection != (byte)0x00);
newPrefs.put(PREF_SOUNDCORE_EQUALIZER_PRESET, String.valueOf(equalizerPreset));
return new GBDeviceEvent[] {
new GBDeviceEventUpdatePreferences(newPrefs),
new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED),
};
}
private GBDeviceEvent[] handlePacketAdaptiveDirection(byte[] payload) {
if (payload.length != 0)
return null;
Prefs prefs = getDevicePrefs();
// Ignore if adaptive direction is enabled
if (prefs.getBoolean(PREF_SOUNDCORE_ADAPTIVE_DIRECTION, true))
return null;
// Set equalizer direction to "standing" and get current configuration
return new GBDeviceEvent[] {
new GBDeviceEventUpdatePreferences(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"),
new GBDeviceEventSendBytes(encodeRequest(CMD_GET_EQUALIZER)),
};
}
private GBDeviceEvent[] handlePacketBatteryInfo(byte[] payload) {
if (payload.length != 1)
return null;
batteryInfo.level = payload[0] * 20;
return new GBDeviceEvent[] { batteryInfo };
}
private GBDeviceEvent[] handlePacketChargingInfo(byte[] payload) {
if (payload.length != 1)
return null;
batteryInfo.state = payload[0] == (byte)0x00 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_CHARGING;
return new GBDeviceEvent[] { batteryInfo };
}
@Override
public byte[] encodeSendConfiguration(String config) {
Prefs prefs = getDevicePrefs();
switch (config) {
case PREF_SOUNDCORE_VOICE_PROMPTS:
return encodeSetBoolean(prefs, PREF_SOUNDCORE_VOICE_PROMPTS, CMD_SET_VOICE_PROMPTS);
case PREF_SOUNDCORE_BUTTON_BRIGHTNESS:
return encodeSetByte(prefs, PREF_SOUNDCORE_BUTTON_BRIGHTNESS, CMD_SET_BUTTON_BRIGHTNESS);
case PREF_SOUNDCORE_AUTO_POWER_OFF:
return encodeSetAutoPowerOff(prefs);
case PREF_SOUNDCORE_LDAC_MODE:
return encodeSetBoolean(prefs, PREF_SOUNDCORE_LDAC_MODE, CMD_SET_LDAC_MODE);
case PREF_SOUNDCORE_ADAPTIVE_DIRECTION:
return encodeSetBoolean(prefs, PREF_SOUNDCORE_ADAPTIVE_DIRECTION, CMD_SET_ADAPTIVE_DIRECTION);
case PREF_SOUNDCORE_EQUALIZER_PRESET:
return encodeSetByte(prefs, PREF_SOUNDCORE_EQUALIZER_PRESET, CMD_SET_EQUALIZER_PRESET);
case PREF_SOUNDCORE_EQUALIZER_DIRECTION:
return encodeRequest(CMD_GET_EQUALIZER);
case PREF_SOUNDCORE_EQUALIZER_BAND1_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND2_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND2_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND3_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND3_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND4_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND4_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND5_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND5_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND6_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND6_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND7_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND7_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND8_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND8_VALUE:
case PREF_SOUNDCORE_EQUALIZER_BAND9_FREQ:
case PREF_SOUNDCORE_EQUALIZER_BAND9_VALUE:
return encodeSetEqualizerCustom(prefs);
}
return super.encodeSendConfiguration(config);
}
private byte[] encodeRequest(short cmd) {
return new SoundcorePacket(cmd).encode();
}
public byte[] encodeGetDeviceInfo() {
return encodeRequest(CMD_GET_DEVICE_INFO);
}
@Override
public byte[] encodePowerOff() {
return encodeRequest(CMD_POWER_OFF);
}
private byte[] encodeSetBoolean(Prefs prefs, String pref, short cmd) {
boolean enabled = prefs.getBoolean(pref, true);
byte[] payload = new byte[] { enabled ? (byte)0x01 : (byte)0x00 };
return new SoundcorePacket(cmd, payload).encode();
}
private byte[] encodeSetByte(Prefs prefs, String pref, short cmd) {
byte value = (byte)Integer.parseInt(prefs.getString(pref, "0"));
byte[] payload = new byte[] { value };
return new SoundcorePacket(cmd, payload).encode();
}
private byte[] encodeSetAutoPowerOff(Prefs prefs) {
byte duration = (byte)Integer.parseInt(prefs.getString(PREF_SOUNDCORE_AUTO_POWER_OFF, "2"));
byte[] payload;
if (duration > 0)
payload = new byte[] { (byte)0x01, (byte)(duration - 1) };
else
payload = new byte[] { (byte)0x00, (byte)0x00 };
return new SoundcorePacket(CMD_SET_AUTO_POWER_OFF, payload).encode();
}
private byte[] encodeSetEqualizerCustom(Prefs prefs) {
ByteBuffer buf = ByteBuffer.allocate(21);
int eqDirection = Integer.parseInt(prefs.getString(PREF_SOUNDCORE_EQUALIZER_DIRECTION, "0"));
byte[] equalizer = equalizerFromPrefs(prefs);
// Bit combination of the equalizer directions that should be changed
buf.put((byte)(1 << eqDirection));
buf.put((byte)0x01);
buf.put((byte)0xff);
buf.put(equalizer);
return new SoundcorePacket(CMD_SET_EQUALIZER_CUSTOM, buf.array()).encode();
}
private Map<String, Object> equalizerToPrefs(byte[] equalizer) {
Map<String, Object> prefs = new HashMap<>();
for (int i = 0; i < EQUALIZER_PREFS_FREQ.length; i++) {
// Equalizer values range from 60 to 180, with 120 as "neutral"
byte freq = equalizer[i * 2 + 1];
int value = Byte.toUnsignedInt(equalizer[i * 2]);
// Map equalizer value to preference range (0 to 120)
prefs.put(EQUALIZER_PREFS_FREQ[i], freq);
prefs.put(EQUALIZER_PREFS_VALUE[i], value - 60);
}
return prefs;
}
private byte[] equalizerFromPrefs(Prefs prefs) {
byte[] equalizer = new byte[EQUALIZER_PREFS_FREQ.length * 2];
for (int i = 0; i < EQUALIZER_PREFS_FREQ.length; i++) {
int freq = Integer.parseInt(prefs.getString(EQUALIZER_PREFS_FREQ[i], "0"));
int value = prefs.getInt(EQUALIZER_PREFS_VALUE[i], 60);
// Map equalizer values from 0 - 120 back to 60 - 180
equalizer[i * 2 + 1] = ((byte)freq);
equalizer[i * 2] = (byte)(value + 60);
}
return equalizer;
}
}

View File

@ -3485,6 +3485,192 @@
<item>AMBIENT_SOUND_CONTROL</item>
</string-array>
<string-array name="soundcore_button_brightness_names">
<item>@string/off</item>
<item>@string/soundcore_button_brightness_low</item>
<item>@string/soundcore_button_brightness_medium</item>
<item>@string/soundcore_button_brightness_high</item>
</string-array>
<string-array name="soundcore_button_brightness_values">
<item>0</item>
<item>20</item>
<item>70</item>
<item>100</item>
</string-array>
<string-array name="soundcore_auto_power_off_names">
<item>@string/pref_button_action_disabled</item>
<item>@string/minutes_5</item>
<item>@string/minutes_10</item>
<item>@string/minutes_20</item>
<item>@string/minutes_60</item>
</string-array>
<string-array name="soundcore_auto_power_off_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
<string-array name="soundcore_equalizer_preset_names">
<item>@string/soundcore_equalizer_preset_signature</item>
<item>@string/soundcore_equalizer_preset_xtra_bass</item>
<item>@string/soundcore_equalizer_preset_voice</item>
<item>@string/soundcore_equalizer_preset_balanced</item>
<item>@string/custom</item>
</string-array>
<string-array name="soundcore_equalizer_preset_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>254</item>
</string-array>
<string-array name="soundcore_equalizer_direction_names">
<item>@string/soundcore_equalizer_direction_standing</item>
<item>@string/soundcore_equalizer_direction_lying</item>
<item>@string/soundcore_equalizer_direction_hanging</item>
</string-array>
<string-array name="soundcore_equalizer_direction_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="soundcore_equalizer_band1_freq_names">
<item>48 Hz</item>
<item>51 Hz</item>
<item>55 Hz</item>
<item>59 Hz</item>
<item>63 Hz</item>
<item>68 Hz</item>
<item>72 Hz</item>
<item>80 Hz</item>
<item>83 Hz</item>
<item>89 Hz</item>
</string-array>
<string-array name="soundcore_equalizer_band2_freq_names">
<item>95 Hz</item>
<item>102 Hz</item>
<item>109 Hz</item>
<item>117 Hz</item>
<item>125 Hz</item>
<item>134 Hz</item>
<item>144 Hz</item>
<item>150 Hz</item>
<item>165 Hz</item>
<item>177 Hz</item>
</string-array>
<string-array name="soundcore_equalizer_band3_freq_names">
<item>190 Hz</item>
<item>203 Hz</item>
<item>218 Hz</item>
<item>233 Hz</item>
<item>250 Hz</item>
<item>268 Hz</item>
<item>287 Hz</item>
<item>300 Hz</item>
<item>330 Hz</item>
<item>353 Hz</item>
</string-array>
<string-array name="soundcore_equalizer_band4_freq_names">
<item>379 Hz</item>
<item>406 Hz</item>
<item>435 Hz</item>
<item>467 Hz</item>
<item>500 Hz</item>
<item>536 Hz</item>
<item>574 Hz</item>
<item>600 Hz</item>
<item>659 Hz</item>
<item>706 Hz</item>
</string-array>
<string-array name="soundcore_equalizer_band5_freq_names">
<item>759 Hz</item>
<item>813 Hz</item>
<item>871 Hz</item>
<item>933 Hz</item>
<item>1.0 kHz</item>
<item>1.07 kHz</item>
<item>1.1 kHz</item>
<item>1.2 kHz</item>
<item>1.3 kHz</item>
<item>1.4 kHz</item>
</string-array>
<string-array name="soundcore_equalizer_band6_freq_names">
<item>1.5 kHz</item>
<item>1.6 kHz</item>
<item>1.75 kHz</item>
<item>1.86 kHz</item>
<item>2.0 kHz</item>
<item>2.15 kHz</item>
<item>2.3 kHz</item>
<item>2.5 kHz</item>
<item>2.6 kHz</item>
<item>2.8 kHz</item>
</string-array>
<string-array name="soundcore_equalizer_band7_freq_names">
<item>3.0 kHz</item>
<item>3.2 kHz</item>
<item>3.5 kHz</item>
<item>3.7 kHz</item>
<item>4.0 kHz</item>
<item>4.3 kHz</item>
<item>4.6 kHz</item>
<item>5.0 kHz</item>
<item>5.3 kHz</item>
<item>5.6 kHz</item>
</string-array>
<string-array name="soundcore_equalizer_band8_freq_names">
<item>6.0 kHz</item>
<item>6.5 kHz</item>
<item>7.0 kHz</item>
<item>7.5 kHz</item>
<item>8.0 kHz</item>
<item>8.5 kHz</item>
<item>9.0 kHz</item>
<item>9.8 kHz</item>
<item>10.5 kHz</item>
<item>11.3 kHz</item>
</string-array>
<string-array name="soundcore_equalizer_band9_freq_names">
<item>12.1 kHz</item>
<item>13.0 kHz</item>
<item>14.0 kHz</item>
<item>15.0 kHz</item>
<item>16.0 kHz</item>
<item>17.1 kHz</item>
<item>18.3 kHz</item>
<item>20.0 kHz</item>
</string-array>
<string-array name="soundcore_equalizer_freq_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
</string-array>
<string-array name="moondrop_equalizer_preset_names">
<item>@string/moondrop_equalizer_preset_reference</item>
<item>@string/moondrop_equalizer_preset_basshead</item>

View File

@ -925,7 +925,9 @@
<string name="minutes_1">1 minute</string>
<string name="minutes_5">5 minutes</string>
<string name="minutes_10">10 minutes</string>
<string name="minutes_20">20 minutes</string>
<string name="minutes_30">30 minutes</string>
<string name="minutes_60">60 minutes</string>
<string name="liveactivity_live_activity">Live activity</string>
<string name="weeksteps_today_steps_description">Steps today, target: %1$s</string>
<string name="lack_of_step">Lack of steps: %1$d</string>
@ -1621,6 +1623,7 @@
<string name="devicetype_sony_linkbuds">Sony LinkBuds</string>
<string name="devicetype_sony_linkbuds_s">Sony LinkBuds S</string>
<string name="devicetype_soundcore_liberty3_pro">Soundcore Liberty 3 Pro</string>
<string name="devicetype_soundcore_motion300">Soundcore Motion 300</string>
<string name="devicetype_moondrop_space_travel">Moondrop Space Travel</string>
<string name="devicetype_binary_sensor">Binary sensor</string>
<string name="devicetype_honor_band3">Honor Band 3</string>
@ -2442,6 +2445,39 @@
<string name="moondrop_touch_action_anc_mode">Switch Active Noise Cancelling Mode</string>
<string name="moondrop_touch_trigger_long_press_1s">Long Press (1s)</string>
<string name="moondrop_touch_trigger_long_press_3s">Long Press (3s)</string>
<string name="soundcore_voice_prompts">Voice Prompts</string>
<string name="soundcore_button_brightness">Button Brightness</string>
<string name="soundcore_button_brightness_low">Low</string>
<string name="soundcore_button_brightness_medium">Medium</string>
<string name="soundcore_button_brightness_high">High</string>
<string name="soundcore_ldac_mode_title">LDAC Mode</string>
<string name="soundcore_ldac_mode_summary">Enabling LDAC will decrease the battery life and might lead to connection instability</string>
<string name="soundcore_adaptive_direction_title">Adaptive Direction</string>
<string name="soundcore_adaptive_direction_summary">Adjust equalizer preset automatically based on device direction</string>
<string name="soundcore_equalizer_preset">Preset</string>
<string name="soundcore_equalizer_preset_signature">soundcore Signature</string>
<string name="soundcore_equalizer_preset_xtra_bass">Xtra Bass</string>
<string name="soundcore_equalizer_preset_voice">Voice</string>
<string name="soundcore_equalizer_preset_balanced">Balanced</string>
<string name="soundcore_equalizer_custom_title">Customize…</string>
<string name="soundcore_equalizer_custom_summary">Configure parametric equalizer settings</string>
<string name="soundcore_equalizer_direction">Device Direction</string>
<string name="soundcore_equalizer_direction_standing">Standing</string>
<string name="soundcore_equalizer_direction_lying">Lying</string>
<string name="soundcore_equalizer_direction_hanging">Hanging</string>
<string name="soundcore_equalizer_reset_title">Reset to default</string>
<string name="soundcore_equalizer_reset_summary">Set all equalizer bands back to default settings</string>
<string name="soundcore_equalizer_band1">Band 1</string>
<string name="soundcore_equalizer_band2">Band 2</string>
<string name="soundcore_equalizer_band3">Band 3</string>
<string name="soundcore_equalizer_band4">Band 4</string>
<string name="soundcore_equalizer_band5">Band 5</string>
<string name="soundcore_equalizer_band6">Band 6</string>
<string name="soundcore_equalizer_band7">Band 7</string>
<string name="soundcore_equalizer_band8">Band 8</string>
<string name="soundcore_equalizer_band9">Band 9</string>
<string name="soundcore_equalizer_frequency">Frequency</string>
<string name="soundcore_equalizer_value">Value</string>
<string name="protocol_version">Protocol Version</string>
<string name="pref_screen_auto_brightness_title">Auto Brightness</string>
<string name="pref_screen_auto_brightness_summary">Adjust screen brightness according to ambient light</string>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:defaultValue="true"
android:icon="@drawable/ic_voice"
android:key="pref_soundcore_voice_prompts"
android:layout="@layout/preference_checkbox"
android:title="@string/soundcore_voice_prompts" />
<ListPreference
android:entries="@array/soundcore_button_brightness_names"
android:entryValues="@array/soundcore_button_brightness_values"
android:icon="@drawable/ic_wb_sunny"
android:key="pref_soundcore_button_brightness"
android:summary="%s"
android:title="@string/soundcore_button_brightness" />
<ListPreference
android:entries="@array/soundcore_auto_power_off_names"
android:entryValues="@array/soundcore_auto_power_off_values"
android:icon="@drawable/ic_power_settings_new"
android:key="pref_soundcore_auto_power_off"
android:summary="%s"
android:title="@string/prefs_wena3_auto_power_off_item" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_music_note"
android:key="pref_soundcore_ldac_mode"
android:layout="@layout/preference_checkbox"
android:title="@string/soundcore_ldac_mode_title"
android:summary="@string/soundcore_ldac_mode_summary" />
<PreferenceCategory android:title="@string/pref_header_equalizer">
<SwitchPreferenceCompat
android:defaultValue="true"
android:icon="@drawable/ic_switch_left"
android:key="pref_soundcore_adaptive_direction"
android:layout="@layout/preference_checkbox"
android:title="@string/soundcore_adaptive_direction_title"
android:summary="@string/soundcore_adaptive_direction_summary" />
<ListPreference
android:entries="@array/soundcore_equalizer_preset_names"
android:entryValues="@array/soundcore_equalizer_preset_values"
android:icon="@drawable/ic_equalizer"
android:key="pref_soundcore_equalizer_preset"
android:summary="%s"
android:title="@string/soundcore_equalizer_preset" />
<PreferenceScreen
android:persistent="false"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_custom"
android:title="@string/soundcore_equalizer_custom_title"
android:summary="@string/soundcore_equalizer_custom_summary">
<ListPreference
android:defaultValue="0"
android:entries="@array/soundcore_equalizer_direction_names"
android:entryValues="@array/soundcore_equalizer_direction_values"
android:icon="@drawable/ic_switch_left"
android:key="pref_soundcore_equalizer_direction"
android:summary="%s"
android:title="@string/soundcore_equalizer_direction" />
<Preference
android:persistent="false"
android:icon="@drawable/ic_history"
android:key="pref_soundcore_equalizer_reset"
android:summary="@string/soundcore_equalizer_reset_summary"
android:title="@string/soundcore_equalizer_reset_title" />
<PreferenceCategory android:title="@string/soundcore_equalizer_band1">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band1_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band1_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band1_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band2">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band2_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band2_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band2_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band3">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band3_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band3_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band3_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band4">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band4_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band4_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band4_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band5">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band5_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band5_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band5_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band6">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band6_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band6_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band6_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band7">
<ListPreference
android:defaultValue="7"
android:entries="@array/soundcore_equalizer_band7_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band7_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band7_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band8">
<ListPreference
android:defaultValue="6"
android:entries="@array/soundcore_equalizer_band8_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band8_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band8_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/soundcore_equalizer_band9">
<ListPreference
android:defaultValue="1"
android:entries="@array/soundcore_equalizer_band9_freq_names"
android:entryValues="@array/soundcore_equalizer_freq_values"
android:icon="@drawable/ic_hearing"
android:key="pref_soundcore_equalizer_band9_freq"
android:summary="%s"
android:title="@string/soundcore_equalizer_frequency" />
<SeekBarPreference
android:defaultValue="60"
android:icon="@drawable/ic_graphic_eq"
android:key="pref_soundcore_equalizer_band9_value"
android:max="120"
android:title="@string/soundcore_equalizer_value"
app:showSeekBarValue="true" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>