diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java index 5567adeaf..7b960df42 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/nothing/NothingProtocol.java @@ -7,13 +7,18 @@ import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; @@ -26,8 +31,9 @@ public class NothingProtocol extends GBDeviceProtocol { final UUID UUID_DEVICE_CTRL = UUID.fromString("aeac4a03-dff5-498f-843a-34487cf133eb"); + private boolean isFirstExchange = true; - public static final byte CONTROL_DEVICE_TYPE_TWS_HEADSET = 1; + private static final byte CONTROL_DEVICE_TYPE_TWS_HEADSET = 1; private static final int CONTROL_CRC = 0x20; @@ -44,15 +50,25 @@ public class NothingProtocol extends GBDeviceProtocol { //incoming private static final short battery_status = (short) 0xe001; private static final short battery_status2 = (short) 0xc007; + private static final short audio_mode_status = (short) 0xc01e; private static final short unk_maybe_ack = (short) 0xf002; private static final short unk_close_case = (short) 0xe002; //sent twice when the case is closed with earphones in //outgoing + private static final short find_device = (short) 0xf002; private static final short in_ear_detection = (short) 0xf004; + private static final short audio_mode = (short) 0xf00f; @Override public GBDeviceEvent[] decodeResponse(byte[] responseData) { + List devEvts = new ArrayList<>(); + + if (isFirstExchange) { + isFirstExchange = false; + devEvts.add(new GBDeviceEventVersionInfo()); //TODO: this is a weird hack to make the DBHelper happy. Replace with proper firmware detection + } + ByteBuffer incoming = ByteBuffer.wrap(responseData); incoming.order(ByteOrder.LITTLE_ENDIAN); @@ -81,7 +97,11 @@ public class NothingProtocol extends GBDeviceProtocol { switch (getRequestCommand(command)) { case battery_status: case battery_status2: - return handleBatteryInfo(payload); + devEvts.add(handleBatteryInfo(payload)); + break; + case audio_mode_status: + devEvts.add(handleAudioModeStatus(payload)); + break; case unk_maybe_ack: LOG.debug("received ack"); @@ -94,14 +114,14 @@ public class NothingProtocol extends GBDeviceProtocol { LOG.debug("Incoming message - control:" + control + " requestCommand: " + (getRequestCommand(command) & 0xffff) + "length: " + length + " dump: " + hexdump(responseData)); } - return null; + return devEvts.toArray(new GBDeviceEvent[devEvts.size()]); } boolean isCrcNeeded(short control) { return (control & CONTROL_CRC) != 0; } - private byte[] encodeMessage(short control, short command, byte[] payload) { + byte[] encodeMessage(short control, short command, byte[] payload) { ByteBuffer msgBuf = ByteBuffer.allocate(8 + payload.length); msgBuf.order(ByteOrder.LITTLE_ENDIAN); @@ -125,7 +145,30 @@ public class NothingProtocol extends GBDeviceProtocol { } byte[] encodeBatteryStatusReq() { - return encodeMessage((short) 0x120, (short) 0xc007, new byte[]{}); + return encodeMessage((short) 0x5120, battery_status2, new byte[]{}); + } + + byte[] encodeAudioModeStatusReq() { + return encodeMessage((short) 0x120, audio_mode_status, new byte[]{}); + } + + //TODO: unify mapping between bytes and strings in the following two functions + private GBDeviceEvent handleAudioModeStatus(byte[] payload) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + SharedPreferences.Editor editor = prefs.edit(); + + if (Arrays.equals(payload, new byte[]{0x01, 0x01, 0x00})) { + editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc").apply(); + } else if (Arrays.equals(payload, new byte[]{0x01, 0x03, 0x00})) { + editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc-light").apply(); + } else if (Arrays.equals(payload, new byte[]{0x01, 0x05, 0x00})) { + editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "off").apply(); + } else if (Arrays.equals(payload, new byte[]{0x01, 0x07, 0x00})) { + editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "transparency").apply(); + } else { + LOG.warn("Unknown audio mode. Payload: " + hexdump(payload)); + } + return null; } byte[] encodeAudioMode(String desired) { @@ -135,19 +178,23 @@ public class NothingProtocol extends GBDeviceProtocol { case "anc": payload[1] = 0x01; break; + case "anc-light": + payload[1] = 0x03; + break; case "transparency": payload[1] = 0x07; break; case "off": default: } - return encodeMessage((short) 0x120, (short) 0xf00f, payload); + return encodeMessage((short) 0x120, audio_mode, payload); } + @Override public byte[] encodeFindDevice(boolean start) { byte payload = (byte) (start ? 0x01 : 0x00); - return encodeMessage((short) 0x120, (short) 0xf002, new byte[]{payload}); + return encodeMessage((short) 0x120, find_device, new byte[]{payload}); } @Override @@ -175,10 +222,11 @@ public class NothingProtocol extends GBDeviceProtocol { public byte[] encodeSetTime() { // This are earphones, there is no time to set here. However this method gets called soon // after connecting, hence we use it to perform some initializations. - return encodeBatteryStatusReq(); + // TODO: Find a way to send more requests during the first connection + return encodeAudioModeStatusReq(); } - private GBDeviceEvent[] handleBatteryInfo(byte[] payload) { + private GBDeviceEvent handleBatteryInfo(byte[] payload) { //LOG.debug("Battery payload: " + hexdump(payload)); /* payload: @@ -202,14 +250,14 @@ public class NothingProtocol extends GBDeviceProtocol { for (int i = 0; i < numBatteries; i++) { evBattery.level += (short) ((payload[2 + 2 * i] & MASK_BATTERY) / numBatteries); if (!batteryCharging) - batteryCharging = ((payload[2 + 2 * i]) & MASK_BATTERY_CHARGING) == 1; + batteryCharging = ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING); //LOG.debug("single battery level: " + hexdump(payload, 2+2*i,1) +"-"+ ((payload[2+2*i] & 0xff))+":" + evBattery.level); } evBattery.state = BatteryState.UNKNOWN; evBattery.state = batteryCharging ? BatteryState.BATTERY_CHARGING : evBattery.state; - return new GBDeviceEvent[]{evBattery}; + return evBattery; } private short getRequestCommand(short command) { diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index a7ebaf031..dfe45f827 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1762,6 +1762,7 @@ anc + anc-light transparency off diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10c82c61f..5c3d566f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1272,4 +1272,7 @@ Take measurements during sleep Frequency of measurements Nothing Ear (1) + Play/pause the music depending if you wear the earbuds + In-Ear detection + Audio mode diff --git a/app/src/main/res/xml/devicesettings_nothing_ear1.xml b/app/src/main/res/xml/devicesettings_nothing_ear1.xml index 2e1a3e84b..2d530472d 100644 --- a/app/src/main/res/xml/devicesettings_nothing_ear1.xml +++ b/app/src/main/res/xml/devicesettings_nothing_ear1.xml @@ -4,13 +4,13 @@ android:defaultValue="true" android:icon="@drawable/ic_extension" android:key="pref_nothing_inear_detection" - android:summary="Play/pause the music depending if you wear the earbuds" - android:title="In-Ear detection" /> + android:summary="@string/nothing_prefs_inear_summary" + android:title="@string/nothing_prefs_inear_title" /> + android:title="@string/nothing_prefs_audiomode_title" />