From c4dc75e6f9424b3009d369894e886e449d5d048e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Thu, 1 Jan 2026 15:57:43 +0000 Subject: [PATCH] Introduce wrappers for SerialDeviceSupport, deprecate old implementation --- .../btbr/AbstractBTBRDeviceSupport.java | 10 +- ...bstractHeadphoneSerialDeviceSupportV2.java | 66 ++++ .../serial/AbstractSerialDeviceSupport.java | 2 + .../serial/AbstractSerialDeviceSupportV2.java | 304 ++++++++++++++++++ .../service/serial/GBDeviceIoThread.java | 4 + .../service/serial/GBDeviceProtocol.java | 5 +- 6 files changed, 387 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractHeadphoneSerialDeviceSupportV2.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupportV2.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java index ac44d9550d..cf44b57c80 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btbr/AbstractBTBRDeviceSupport.java @@ -22,7 +22,6 @@ import android.os.ParcelUuid; import org.slf4j.Logger; -import java.io.IOException; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.Logging; @@ -67,8 +66,7 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im final UUID supportedService = getSupportedService(); if (supportedService == null) { // Before throwing the exception, list the available UUIDs - final BluetoothDevice btDevice = getBluetoothAdapter().getRemoteDevice(gbDevice.getAddress()); - @SuppressLint("MissingPermission") final ParcelUuid[] uuids = btDevice.getUuids(); + final ParcelUuid[] uuids = getBluetoothDeviceUuids(); if (uuids == null || uuids.length == 0) { logger.warn("Device provided no UUIDs to connect to: {}", gbDevice); } else { @@ -91,6 +89,12 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im } } + @SuppressLint("MissingPermission") + protected ParcelUuid[] getBluetoothDeviceUuids() { + final BluetoothDevice btDevice = getBluetoothAdapter().getRemoteDevice(gbDevice.getAddress()); + return btDevice.getUuids(); + } + public void disconnect() { synchronized (ConnectionMonitor) { if (mQueue != null) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractHeadphoneSerialDeviceSupportV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractHeadphoneSerialDeviceSupportV2.java new file mode 100644 index 0000000000..daca2784ac --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractHeadphoneSerialDeviceSupportV2.java @@ -0,0 +1,66 @@ +/* Copyright (C) 2025 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.serial; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.service.AbstractHeadphoneBTBRDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.HeadphoneHelper; + +/** + * @deprecated Use {@link AbstractHeadphoneBTBRDeviceSupport} + */ +@Deprecated +public abstract class AbstractHeadphoneSerialDeviceSupportV2 + extends AbstractSerialDeviceSupportV2 implements HeadphoneHelper.Callback { + private HeadphoneHelper headphoneHelper; + + @Override + public void onSetCallState(final CallSpec callSpec) { + headphoneHelper.onSetCallState(callSpec); + } + + @Override + public void onNotification(final NotificationSpec notificationSpec) { + headphoneHelper.onNotification(notificationSpec); + } + + @Override + public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) { + super.setContext(gbDevice, btAdapter, context); + headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this); + } + + @Override + public void onSendConfiguration(final String config) { + if (!headphoneHelper.onSendConfiguration(config)) + super.onSendConfiguration(config); + } + + @Override + public void dispose() { + synchronized (ConnectionMonitor) { + if (headphoneHelper != null) + headphoneHelper.dispose(); + super.dispose(); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index c1b9f0ed2b..dff9c9c13f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -51,7 +51,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; *

* This implementation implements all methods of {@link EventHandler}, calls the {@link GBDeviceProtocol device protocol} * to create the device specific message for the respective events and sends them to the device via {@link #sendToDevice(byte[])}. + * @deprecated Use {@link nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport} */ +@Deprecated public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(AbstractSerialDeviceSupport.class); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupportV2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupportV2.java new file mode 100644 index 0000000000..6f8f26e5ad --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupportV2.java @@ -0,0 +1,304 @@ +package nodomain.freeyourgadget.gadgetbridge.service.serial; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.location.Location; +import android.os.ParcelUuid; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.Reminder; +import nodomain.freeyourgadget.gadgetbridge.model.WorldClock; +import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder; + +/** + * This class serves only to help existing implementations transition from AbstractSerialDeviceSupport, + * and should not be used for new implementations. + * + * @deprecated Use {@link nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport} + */ +@Deprecated +public abstract class AbstractSerialDeviceSupportV2 extends AbstractBTBRDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(AbstractSerialDeviceSupportV2.class); + + protected T mDeviceProtocol; + + public AbstractSerialDeviceSupportV2() { + this(2048); + } + + public AbstractSerialDeviceSupportV2(final int bufferSize) { + super(LOG, bufferSize); + } + + protected abstract T createDeviceProtocol(); + + @Override + protected UUID getSupportedService() { + // Preserve the behavior from legacy AbstractSerialDeviceSupport, and return the first UUID + final UUID supportedService = super.getSupportedService(); + if (supportedService != null) { + return supportedService; + } + final ParcelUuid[] bluetoothDeviceUuids = getBluetoothDeviceUuids(); + if (bluetoothDeviceUuids != null && bluetoothDeviceUuids.length > 0) { + return bluetoothDeviceUuids[0].getUuid(); + } + return null; + } + + @Override + public boolean useAutoConnect() { + return false; + } + + @Override + public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) { + super.setContext(gbDevice, btAdapter, context); + this.mDeviceProtocol = createDeviceProtocol(); + } + + @Override + public void onSocketRead(final byte[] data) { + final GBDeviceEvent[] deviceEvents = mDeviceProtocol.decodeResponse(data); + if (deviceEvents == null) { + LOG.debug("unhandled message"); + return; + } + + for (GBDeviceEvent deviceEvent : deviceEvents) { + if (deviceEvent == null) { + continue; + } + evaluateGBDeviceEvent(deviceEvent); + } + } + + private void sendToDevice(final byte[] bytes) { + if (bytes != null) { + final TransactionBuilder builder = createTransactionBuilder("send bytes"); + builder.write(bytes); + builder.queue(); + } + } + + private void handleGBDeviceEvent(final GBDeviceEventSendBytes sendBytes) { + sendToDevice(sendBytes.encodedBytes); + } + + @Override + public void evaluateGBDeviceEvent(final GBDeviceEvent deviceEvent) { + if (deviceEvent instanceof GBDeviceEventSendBytes) { + handleGBDeviceEvent((GBDeviceEventSendBytes) deviceEvent); + return; + } + super.evaluateGBDeviceEvent(deviceEvent); + } + + @Override + public void onNotification(final NotificationSpec notificationSpec) { + byte[] bytes = mDeviceProtocol.encodeNotification(notificationSpec); + sendToDevice(bytes); + } + + @Override + public void onDeleteNotification(final int id) { + byte[] bytes = mDeviceProtocol.encodeDeleteNotification(id); + sendToDevice(bytes); + } + + @Override + public void onSetTime() { + byte[] bytes = mDeviceProtocol.encodeSetTime(); + sendToDevice(bytes); + } + + @Override + public void onSetCallState(final CallSpec callSpec) { + byte[] bytes = mDeviceProtocol.encodeSetCallState(callSpec.number, callSpec.name, callSpec.command); + sendToDevice(bytes); + } + + @Override + public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) { + byte[] bytes = mDeviceProtocol.encodeSetCannedMessages(cannedMessagesSpec); + sendToDevice(bytes); + } + + @Override + public void onSetMusicState(final MusicStateSpec stateSpec) { + byte[] bytes = mDeviceProtocol.encodeSetMusicState(stateSpec.state, stateSpec.position, stateSpec.playRate, stateSpec.shuffle, stateSpec.repeat); + sendToDevice(bytes); + } + + @Override + public void onSetMusicInfo(final MusicSpec musicSpec) { + byte[] bytes = mDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr); + sendToDevice(bytes); + } + + @Override + public void onSetPhoneVolume(final float volume) { + byte[] bytes = mDeviceProtocol.encodeVolume(volume); + sendToDevice(bytes); + } + + @Override + public void onAppInfoReq() { + byte[] bytes = mDeviceProtocol.encodeAppInfoReq(); + sendToDevice(bytes); + } + + @Override + public void onAppStart(final UUID uuid, final boolean start) { + byte[] bytes = mDeviceProtocol.encodeAppStart(uuid, start); + sendToDevice(bytes); + } + + @Override + public void onAppDelete(final UUID uuid) { + byte[] bytes = mDeviceProtocol.encodeAppDelete(uuid); + sendToDevice(bytes); + } + + @Override + public void onAppReorder(final UUID[] uuids) { + byte[] bytes = mDeviceProtocol.encodeAppReorder(uuids); + sendToDevice(bytes); + } + + @Override + public void onFetchRecordedData(final int dataTypes) { + byte[] bytes = mDeviceProtocol.encodeFetchRecordedData(dataTypes); + sendToDevice(bytes); + } + + @Override + public void onReset(final int flags) { + byte[] bytes = mDeviceProtocol.encodeReset(flags); + sendToDevice(bytes); + } + + @Override + public void onFindDevice(final boolean start) { + byte[] bytes = mDeviceProtocol.encodeFindDevice(start); + sendToDevice(bytes); + } + + @Override + public void onFindPhone(final boolean start) { + byte[] bytes = mDeviceProtocol.encodeFindPhone(start); + sendToDevice(bytes); + } + + @Override + public void onScreenshotReq() { + byte[] bytes = mDeviceProtocol.encodeScreenshotReq(); + sendToDevice(bytes); + } + + @Override + public void onEnableRealtimeSteps(final boolean enable) { + byte[] bytes = mDeviceProtocol.encodeEnableRealtimeSteps(enable); + sendToDevice(bytes); + } + + @Override + public void onEnableHeartRateSleepSupport(final boolean enable) { + byte[] bytes = mDeviceProtocol.encodeEnableHeartRateSleepSupport(enable); + sendToDevice(bytes); + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(final boolean enable) { + byte[] bytes = mDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable); + sendToDevice(bytes); + } + + @Override + public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) { + byte[] bytes = mDeviceProtocol.encodeAddCalendarEvent(calendarEventSpec); + sendToDevice(bytes); + } + + @Override + public void onDeleteCalendarEvent(final byte type, final long id) { + byte[] bytes = mDeviceProtocol.encodeDeleteCalendarEvent(type, id); + sendToDevice(bytes); + } + + @Override + public void onSendConfiguration(final String config) { + byte[] bytes = mDeviceProtocol.encodeSendConfiguration(config); + sendToDevice(bytes); + } + + @Override + public void onTestNewFunction() { + byte[] bytes = mDeviceProtocol.encodeTestNewFunction(); + sendToDevice(bytes); + } + + @Override + public void onSendWeather() { + byte[] bytes = mDeviceProtocol.encodeSendWeather(); + sendToDevice(bytes); + } + + @Override + public void onSetFmFrequency(final float frequency) { + byte[] bytes = mDeviceProtocol.encodeFmFrequency(frequency); + sendToDevice(bytes); + } + + @Override + public void onSetLedColor(final int color) { + byte[] bytes = mDeviceProtocol.encodeLedColor(color); + sendToDevice(bytes); + } + + @Override + public void onPowerOff() { + byte[] bytes = mDeviceProtocol.encodePowerOff(); + sendToDevice(bytes); + } + + @Override + public void onSetAlarms(final ArrayList alarms) { + byte[] bytes = mDeviceProtocol.encodeSetAlarms(alarms); + sendToDevice(bytes); + } + + @Override + public void onSetReminders(final ArrayList reminders) { + byte[] bytes = mDeviceProtocol.encodeReminders(reminders); + sendToDevice(bytes); + } + + @Override + public void onSetWorldClocks(final ArrayList clocks) { + byte[] bytes = mDeviceProtocol.encodeWorldClocks(clocks); + sendToDevice(bytes); + } + + @Override + public void onSetGpsLocation(final Location location) { + byte[] bytes = mDeviceProtocol.encodeGpsLocation(location); + sendToDevice(bytes); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java index 74eaabf8a3..9219e819e4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java @@ -22,6 +22,10 @@ import java.util.concurrent.atomic.AtomicLong; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +/** + * @deprecated Use {@link nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport} + */ +@Deprecated public abstract class GBDeviceIoThread extends Thread { private static final AtomicLong THREAD_COUNTER = new AtomicLong(0L); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index 3442508ac0..d4621ba98d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -32,10 +32,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.Reminder; -import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WorldClock; import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs; +/** + * @deprecated Use {@link nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport} + */ +@Deprecated public abstract class GBDeviceProtocol { public static final int RESET_FLAGS_REBOOT = 1;