From a03bfee348b01a67516259ef272fc51cdd5bc5fd Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Wed, 1 Apr 2026 22:36:28 +0200 Subject: [PATCH] Fossil Q: Add/fix button actions for music and volume control --- .../adapter/fossil/FossilWatchAdapter.java | 89 ++++++++++++++++--- .../fossil_hr/FossilHRWatchAdapter.java | 49 +--------- .../qhybrid/buttonconfig/ConfigPayload.java | 7 +- app/src/main/res/values/arrays.xml | 20 +++-- app/src/main/res/values/strings.xml | 10 +++ 5 files changed, 105 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java index 6341d0365c..43f9707f3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil/FossilWatchAdapter.java @@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper; @@ -88,6 +89,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FirmwareFilePutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.AnimationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.MoveHandsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.ReleaseHandsControlRequest; @@ -801,13 +803,15 @@ public class FossilWatchAdapter extends WatchAdapter { } protected void handleBackgroundCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value) { - switch (value[1]) { - case 2: { + byte requestType = value[1]; + + switch (requestType) { + case 0x02: { byte syncId = value[2]; getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(QHybridSupport.ITEM_LAST_HEARTBEAT, DateFormat.getTimeInstance().format(new Date()))); break; } - case 8: { + case 0x08: { // FORWARD_TO_PHONE if (value.length != 12) { throw new RuntimeException("wrong button message"); } @@ -819,34 +823,93 @@ public class FossilWatchAdapter extends WatchAdapter { log("Button press on button " + button); Intent i = new Intent(QHYBRID_EVENT_BUTTON_PRESS); - i.setPackage(BuildConfig.APPLICATION_ID); i.putExtra("BUTTON", button); getContext().sendBroadcast(i); + + LOG.debug("Sent broadcast intent: ACTION={} EXTRA=BUTTON={}", QHYBRID_EVENT_BUTTON_PRESS, button); } break; } - case 5: { + case 0x05: { // FORWARD_TO_PHONE_MULTI / VOLUME_UP / VOLUME_DOWN / MUSIC_CONTROL if (value.length != 4) { throw new RuntimeException("wrong button message"); } int action = value[3]; - String actionString = "SINGLE"; - if (action == 3) actionString = "DOUBLE"; - else if (action == 4) actionString = "LONG"; + String upperButtonFunction = getDeviceSpecificPreferences().getString("top_button_function", ""); + String middleButtonFunction = getDeviceSpecificPreferences().getString("middle_button_function", ""); + String bottomButtonFunction = getDeviceSpecificPreferences().getString("bottom_button_function", ""); + boolean musicControlEnabled = upperButtonFunction.equals("MUSIC_CONTROL") || middleButtonFunction.equals("MUSIC_CONTROL") || bottomButtonFunction.equals("MUSIC_CONTROL"); + MusicControlRequest.MUSIC_WATCH_REQUEST request = MusicControlRequest.MUSIC_WATCH_REQUEST.fromCommandByte((byte)action); + GBDevice currentDevice = getDeviceSupport().getDevice(); + if (((QHybridCoordinator) currentDevice.getDeviceCoordinator()).isHybridHR(currentDevice) + || request == MusicControlRequest.MUSIC_WATCH_REQUEST.MUSIC_REQUEST_LOUDER + || request == MusicControlRequest.MUSIC_WATCH_REQUEST.MUSIC_REQUEST_QUITER + || (musicControlEnabled && request == MusicControlRequest.MUSIC_WATCH_REQUEST.MUSIC_REQUEST_PLAY_PAUSE) + || (musicControlEnabled && request == MusicControlRequest.MUSIC_WATCH_REQUEST.MUSIC_REQUEST_NEXT) + || (musicControlEnabled && request == MusicControlRequest.MUSIC_WATCH_REQUEST.MUSIC_REQUEST_PREVIOUS) + ) { + handleMusicRequest(value); + } else { + String actionString = "SINGLE"; + if (action == 3) actionString = "DOUBLE"; + else if (action == 4) actionString = "LONG"; - // lastButtonIndex = index; - log(actionString + " button press"); + // lastButtonIndex = index; + log(actionString + " button press"); - Intent i = new Intent(QHYBRID_EVENT_MULTI_BUTTON_PRESS); - i.putExtra("ACTION", actionString); - getContext().sendBroadcast(i); + Intent i = new Intent(QHYBRID_EVENT_MULTI_BUTTON_PRESS); + i.putExtra("ACTION", actionString); + getContext().sendBroadcast(i); + + LOG.debug("Sent broadcast intent: ACTION={} EXTRA=ACTION={}", QHYBRID_EVENT_MULTI_BUTTON_PRESS, actionString); + } break; } } } + private void handleMusicRequest(byte[] value) { + byte command = value[3]; + LOG.info("got music command: " + command); + MusicControlRequest.MUSIC_WATCH_REQUEST request = MusicControlRequest.MUSIC_WATCH_REQUEST.fromCommandByte(command); + + GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl(); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY; + + // TODO add skipping/seeking + + switch (request) { + case MUSIC_REQUEST_PLAY_PAUSE: { + queueWrite(new MusicControlRequest(MusicControlRequest.MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PLAY_PAUSE)); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE; + break; + } + case MUSIC_REQUEST_NEXT: { + queueWrite(new MusicControlRequest(MusicControlRequest.MUSIC_PHONE_REQUEST.MUSIC_REQUEST_NEXT)); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT; + break; + } + case MUSIC_REQUEST_PREVIOUS: { + queueWrite(new MusicControlRequest(MusicControlRequest.MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PREVIOUS)); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS; + break; + } + case MUSIC_REQUEST_LOUDER: { + queueWrite(new MusicControlRequest(MusicControlRequest.MUSIC_PHONE_REQUEST.MUSIC_REQUEST_LOUDER)); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP; + break; + } + case MUSIC_REQUEST_QUITER: { + queueWrite(new MusicControlRequest(MusicControlRequest.MUSIC_PHONE_REQUEST.MUSIC_REQUEST_QUITER)); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN; + break; + } + } + + getDeviceSupport().evaluateGBDeviceEvent(deviceEventMusicControl); + } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java index 1f9f3b6d60..f645a40f38 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java @@ -27,7 +27,6 @@ import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.reque import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.UnitsConfigItem; import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.VibrationStrengthConfigItem; import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_WATCH_REQUEST; import static nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil.convertDrawableToBitmap; import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID; import static nodomain.freeyourgadget.gadgetbridge.util.StringUtils.shortenPackageName; @@ -103,7 +102,6 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.CommuteActionsActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader; @@ -130,9 +128,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; -import nodomain.freeyourgadget.gadgetbridge.webview.CurrentPosition; +import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle; @@ -196,6 +193,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; import nodomain.freeyourgadget.gadgetbridge.util.Version; import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent; import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager; +import nodomain.freeyourgadget.gadgetbridge.webview.CurrentPosition; public class FossilHRWatchAdapter extends FossilWatchAdapter { public static final int MESSAGE_WHAT_VOICE_DATA_RECEIVED = 0; @@ -1964,8 +1962,6 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } else if (value[7] == 0x03) { handleQuickReplyRequest(value); } - } else if (requestType == (byte) 0x05) { - handleMusicRequest(value); } else if (requestType == (byte) 0x01) { int eventId = value[2]; LOG.info("got event id " + eventId); @@ -2143,47 +2139,6 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { queueWrite(new QuickReplyConfirmationPutRequest(callId)); } - private void handleMusicRequest(byte[] value) { - byte command = value[3]; - LOG.info("got music command: " + command); - MUSIC_WATCH_REQUEST request = MUSIC_WATCH_REQUEST.fromCommandByte(command); - - GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl(); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY; - - // TODO add skipping/seeking - - switch (request) { - case MUSIC_REQUEST_PLAY_PAUSE: { - queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PLAY_PAUSE)); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE; - break; - } - case MUSIC_REQUEST_NEXT: { - queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_NEXT)); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT; - break; - } - case MUSIC_REQUEST_PREVIOUS: { - queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_PREVIOUS)); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS; - break; - } - case MUSIC_REQUEST_LOUDER: { - queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_LOUDER)); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP; - break; - } - case MUSIC_REQUEST_QUITER: { - queueWrite(new MusicControlRequest(MUSIC_PHONE_REQUEST.MUSIC_REQUEST_QUITER)); - deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN; - break; - } - } - - getDeviceSupport().evaluateGBDeviceEvent(deviceEventMusicControl); - } - @Override public void setCommuteMenuMessage(String message, boolean finished) { queueWrite(new SetCommuteMenuMessage(message, finished, this)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java index d86adca2df..b1d936be12 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/buttonconfig/ConfigPayload.java @@ -27,7 +27,12 @@ public enum ConfigPayload { new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x0C, (byte) 0x2E, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x8B, (byte) 0x00, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x08, (byte) 0x01, (byte) 0x14, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0xBF, (byte) 0xD5, (byte) 0x54, (byte) 0xD1} ), FORWARD_TO_PHONE_MULTI( - "forward to phone (multifunction)", // This is "play/pause music" in the official app + "forward to phone (multifunction)", // Identical to MUSIC_CONTROL, but handled differently in FossilWatchAdapter + new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00}, + new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97} + ), + MUSIC_CONTROL( + "control music (play/pause/prev/next", // Identical to FORWARD_TO_PHONE_MULTI, but handled differently in FossilWatchAdapter new byte[]{(byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x00}, new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x06, (byte) 0x12, (byte) 0x63, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x05, (byte) 0x01, (byte) 0x1D, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0xF6, (byte) 0x00, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x42, (byte) 0x02, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x44, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x1E, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xCD, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x03, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB6, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x0D, (byte) 0x00, (byte) 0x8C, (byte) 0x01, (byte) 0xB5, (byte) 0x00, (byte) 0x01, (byte) 0x93, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0xFE, (byte) 0x08, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x7B, (byte) 0x56, (byte) 0x4E, (byte) 0x97} ), diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 8f6651d078..d77245d6d8 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -5439,15 +5439,16 @@ - Forward to phone - Forward to phone (multifunction) - Stopwatch - Date - Last notification - Second timezone - Volume up - Volume down - Step goal progress + @string/qhybrid_button_function_forward + @string/qhybrid_button_function_forward_multi + @string/qhybrid_button_function_stopwatch + @string/qhybrid_button_function_date + @string/qhybrid_button_function_last_notif + @string/qhybrid_button_function_2nd_tz + @string/qhybrid_button_function_volume_up + @string/qhybrid_button_function_volume_down + @string/qhybrid_button_function_step_goal + @string/qhybrid_button_function_control_music @@ -5461,6 +5462,7 @@ VOLUME_UP VOLUME_DOWN STEP_GOAL_COMPLETION + MUSIC_CONTROL \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ddcae88e0b..cb6384a728 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4714,4 +4714,14 @@ Top button function Middle button function Bottom button function + Forward to phone + Forward to phone (multifunction) + Stopwatch + Date + Last notification + Second timezone + Volume up + Volume down + Step goal progress + Control music player