1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-02 03:16:07 +02:00

Nothing Ear (Stick): Initial support

- Refactor common coordinator logic to AbstractEarCoordinator
- Increment message counter on the stick
- Make audio modes translatable
This commit is contained in:
José Rebelo 2024-01-05 09:35:03 +00:00
parent fa9c474e61
commit 05d8f99312
10 changed files with 279 additions and 136 deletions

View File

@ -0,0 +1,101 @@
/* Copyright (C) 2023 Daniele Gobbetti, 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.nothing;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.Ear1Support;
public abstract class AbstractEarCoordinator extends AbstractBLClassicDeviceCoordinator {
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public String getManufacturer() {
return "Nothing";
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public int getBatteryCount() {
return 3;
}
@Override
public BatteryConfig[] getBatteryConfig() {
BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
return new BatteryConfig[]{battery1, battery2, battery3};
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return Ear1Support.class;
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_nothing_ear1
};
}
@Override
public int getDefaultIconResource() {
return R.drawable.ic_device_nothingear;
}
@Override
public int getDisabledIconResource() {
return R.drawable.ic_device_nothingear_disabled;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new EarSettingsCustomizer();
}
public abstract boolean incrementCounter();
public abstract boolean supportsLightAncAndTransparency();
}

View File

@ -1,87 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.Ear1Support;
public class Ear1Coordinator extends AbstractBLClassicDeviceCoordinator {
public class Ear1Coordinator extends AbstractEarCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Nothing ear (1)", Pattern.LITERAL);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public String getManufacturer() {
return "Nothing";
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public int getBatteryCount() {
return 3;
}
@Override
public BatteryConfig[] getBatteryConfig() {
BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
return new BatteryConfig[]{battery1, battery2, battery3};
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return Ear1Support.class;
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[] {
R.xml.devicesettings_nothing_ear1
};
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_nothingear1;
}
@Override
public int getDefaultIconResource() {
return R.drawable.ic_device_nothingear;
public boolean incrementCounter() {
return false;
}
@Override
public int getDisabledIconResource() {
return R.drawable.ic_device_nothingear_disabled;
public boolean supportsLightAncAndTransparency() {
return true;
}
}

View File

@ -16,88 +16,28 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.nothing;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.Ear1Support;
public class Ear2Coordinator extends AbstractBLClassicDeviceCoordinator {
public class Ear2Coordinator extends AbstractEarCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Ear (2)", Pattern.LITERAL);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public String getManufacturer() {
return "Nothing";
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public int getBatteryCount() {
return 3;
}
@Override
public BatteryConfig[] getBatteryConfig() {
BatteryConfig battery1 = new BatteryConfig(0, R.drawable.ic_tws_case, R.string.battery_case);
BatteryConfig battery2 = new BatteryConfig(1, R.drawable.ic_nothing_ear_l, R.string.left_earbud);
BatteryConfig battery3 = new BatteryConfig(2, R.drawable.ic_nothing_ear_r, R.string.right_earbud);
return new BatteryConfig[]{battery1, battery2, battery3};
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass() {
return Ear1Support.class;
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[] {
R.xml.devicesettings_nothing_ear1
};
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_nothingear2;
}
@Override
public int getDefaultIconResource() {
return R.drawable.ic_device_nothingear;
public boolean incrementCounter() {
return false;
}
@Override
public int getDisabledIconResource() {
return R.drawable.ic_device_nothingear_disabled;
public boolean supportsLightAncAndTransparency() {
return true;
}
}

View File

@ -0,0 +1,92 @@
/* Copyright (C) 2023 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.nothing;
import android.os.Parcel;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class EarSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
@Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
final AbstractEarCoordinator earCoordinator = (AbstractEarCoordinator) handler.getDevice().getDeviceCoordinator();
if (!earCoordinator.supportsLightAncAndTransparency()) {
// If light anc and transparency is not supported, remove the values from the preference
final Preference audioModePref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE);
if (audioModePref != null) {
final CharSequence[] originalEntries = ((ListPreference) audioModePref).getEntries();
final CharSequence[] originalEntryValues = ((ListPreference) audioModePref).getEntryValues();
final List<CharSequence> entries = new ArrayList<>();
final List<CharSequence> entryValues = new ArrayList<>();
for (int i = 0; i < originalEntries.length; i++) {
if ("anc".equals(originalEntryValues[i].toString()) || "off".equals(originalEntryValues[i].toString())) {
entries.add(originalEntries[i]);
entryValues.add(originalEntryValues[i]);
}
}
((ListPreference) audioModePref).setEntries(entries.toArray(new CharSequence[0]));
((ListPreference) audioModePref).setEntryValues(entryValues.toArray(new CharSequence[0]));
}
}
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
public static final Creator<EarSettingsCustomizer> CREATOR = new Creator<EarSettingsCustomizer>() {
@Override
public EarSettingsCustomizer createFromParcel(final Parcel in) {
return new EarSettingsCustomizer();
}
@Override
public EarSettingsCustomizer[] newArray(final int size) {
return new EarSettingsCustomizer[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2023 Daniele Gobbetti, 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.nothing;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
public class EarStickCoordinator extends AbstractEarCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Ear (stick)", Pattern.LITERAL);
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_nothingearstick;
}
@Override
public boolean incrementCounter() {
return true;
}
@Override
public boolean supportsLightAncAndTransparency() {
return false;
}
}

View File

@ -115,6 +115,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoord
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear2Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.EarStickCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator;
@ -279,6 +280,7 @@ public enum DeviceType {
DOMYOS_T540(DomyosT540Coordinator.class),
NOTHING_EAR1(Ear1Coordinator.class),
NOTHING_EAR2(Ear2Coordinator.class),
NOTHING_EAR_STICK(EarStickCoordinator.class),
GALAXY_BUDS_PRO(GalaxyBudsProDeviceCoordinator.class),
GALAXY_BUDS_LIVE(GalaxyBudsLiveDeviceCoordinator.class),
GALAXY_BUDS(GalaxyBudsDeviceCoordinator.class),

View File

@ -20,6 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.AbstractEarCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
@ -61,6 +62,8 @@ public class NothingProtocol extends GBDeviceProtocol {
private static final short in_ear_detection = (short) 0xf004;
private static final short audio_mode = (short) 0xf00f;
private final boolean incrementCounter;
private int messageCounter = 0x00;
private HashMap<Byte, GBDeviceEventBatteryInfo> batteries;
private static final byte battery_earphone_left = 0x02;
@ -133,10 +136,16 @@ public class NothingProtocol extends GBDeviceProtocol {
ByteBuffer msgBuf = ByteBuffer.allocate(8 + payload.length);
msgBuf.order(ByteOrder.LITTLE_ENDIAN);
msgBuf.put((byte) 0x55); //sof
msgBuf.putShort(control);
msgBuf.putShort((short) (incrementCounter ? (control | 0x40) : control));
msgBuf.putShort(command);
msgBuf.putShort((short) payload.length);
msgBuf.put((byte) 0x00); //fsn TODO: is this always 0?
msgBuf.put((byte) messageCounter); //fsn
if (incrementCounter) {
messageCounter++;
if ((byte) messageCounter == (byte) 0xfd) {
messageCounter = 0x00;
}
}
msgBuf.put(payload);
if (isCrcNeeded(control)) {
@ -294,8 +303,13 @@ public class NothingProtocol extends GBDeviceProtocol {
return (byte) ((control & MASK_DEVICE_TYPE) >> 8);
}
private AbstractEarCoordinator getCoordinator() {
return (AbstractEarCoordinator) getDevice().getDeviceCoordinator();
}
protected NothingProtocol(GBDevice device) {
super(device);
batteries = new HashMap<>(3);
batteries.put(battery_earphone_left, new GBDeviceEventBatteryInfo());
@ -306,5 +320,6 @@ public class NothingProtocol extends GBDeviceProtocol {
batteries.get(battery_earphone_left).batteryIndex=1;
batteries.get(battery_earphone_right).batteryIndex=2;
incrementCounter = getCoordinator().incrementCounter();
}
}

View File

@ -2820,8 +2820,15 @@
<item>2</item>
<item>3</item>
</string-array>
<string-array name="nothing_ear1_audio_mode">
<string-array name="nothing_ear1_audio_mode_names">
<item>@string/prefs_active_noise_cancelling</item>
<item>@string/prefs_active_noise_cancelling_light</item>
<item>@string/prefs_active_noise_cancelling_transparency</item>
<item>@string/off</item>
</string-array>
<string-array name="nothing_ear1_audio_mode_values">
<item>anc</item>
<item>anc-light</item>
<item>transparency</item>

View File

@ -1961,6 +1961,7 @@
<string name="prefs_autoheartrate_interval">Frequency of measurements</string>
<string name="devicetype_nothingear1">Nothing Ear (1)</string>
<string name="devicetype_nothingear2">Nothing Ear (2)</string>
<string name="devicetype_nothingearstick">Nothing Ear (Stick)</string>
<string name="devicetype_galaxybuds">Galaxy Buds</string>
<string name="devicetype_galaxybuds_live">Galaxy Buds Live</string>
<string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
@ -2001,6 +2002,8 @@
<string name="prefs_ambient_mode">Ambient Mode</string>
<string name="prefs_ambient_settings_title">Ambient Sound Options</string>
<string name="prefs_active_noise_cancelling">Active Noise Cancelling</string>
<string name="prefs_active_noise_cancelling_light">Light Active Noise Cancelling</string>
<string name="prefs_active_noise_cancelling_transparency">Transparency</string>
<string name="prefs_active_noise_cancelling_level">Active Noise Cancelling Level</string>
<string name="prefs_active_noise_cancelling_level_high">High</string>
<string name="prefs_active_noise_cancelling_level_low">Low</string>

View File

@ -8,8 +8,8 @@
android:title="@string/nothing_prefs_inear_title" />
<ListPreference
android:icon="@drawable/ic_extension"
android:entryValues="@array/nothing_ear1_audio_mode"
android:entries="@array/nothing_ear1_audio_mode"
android:entryValues="@array/nothing_ear1_audio_mode_values"
android:entries="@array/nothing_ear1_audio_mode_names"
android:key="pref_nothing_audiomode"
android:summary="%s"
android:title="@string/nothing_prefs_audiomode_title" />