diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java index 6335c8f40..bde32b448 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java @@ -36,6 +36,8 @@ public class NotificationCommand extends BaseCommand { public static final byte EXTENDED_SERVICE_TYPE_LINE = 5; public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6; + public static final int MAX_PAYLOAD_LENGTH = 13; + private byte serviceType; private byte totalPieces; private byte currentPiece; @@ -49,7 +51,7 @@ public class NotificationCommand extends BaseCommand { public void setServiceType(int type) { if (type < 0 || type > 4) throw new IllegalArgumentException("Invalid service type"); - this.serviceType = (byte)(1 << type); + this.serviceType = (byte) (1 << type); } public byte getTotalPieces() { @@ -103,8 +105,9 @@ public class NotificationCommand extends BaseCommand { @Override protected byte serializeParams(ByteBuffer params) { boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0 - && (extendedServiceType & 0x0f) != 0 ; - int maxPayloadLength = hasExtendedServiceType ? 12 : 13; + && (extendedServiceType & 0x0f) != 0; + int maxPayloadLength = MAX_PAYLOAD_LENGTH; + if (hasExtendedServiceType) maxPayloadLength -= 1; if (payload.length > maxPayloadLength) throw new IllegalStateException("Payload is too long"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 427622a87..a39b63c75 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -71,6 +71,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFi import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.AbstractSendNotificationRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendCallNotificationRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest; @@ -126,7 +129,16 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { - + try { + TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName()); + SendNotificationRequest request = new SendNotificationRequest(this, builder); + request.setNotification(notificationSpec); + request.perform(); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to send notification", Toast.LENGTH_SHORT, + GB.ERROR, e); + } } @Override @@ -173,7 +185,20 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSetCallState(CallSpec callSpec) { - + switch (callSpec.command) { + case CallSpec.CALL_INCOMING: + try { + TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName()); + SendCallNotificationRequest request = new SendCallNotificationRequest(this, builder); + request.setCallNotification(callSpec); + request.perform(); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to send call notification", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + break; + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java new file mode 100644 index 000000000..acfb01935 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java @@ -0,0 +1,98 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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.devices.lefun.requests; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public abstract class AbstractSendNotificationRequest extends Request { + protected AbstractSendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + protected abstract String getMessage(); + + protected abstract byte getNotificationType(); + + protected abstract byte getExtendedNotificationType(); + + @Override + public byte[] createRequest() { + return new byte[0]; + } + + @Override + protected void doPerform() throws IOException { + byte notificationType = getNotificationType(); + byte extendedNotificationType = getExtendedNotificationType(); + boolean reserveSpaceForExtended = notificationType == NotificationCommand.SERVICE_TYPE_EXTENDED; + byte[] encoded = getMessage().getBytes(StandardCharsets.UTF_8); + ByteBuffer buffer = ByteBuffer.wrap(encoded); + + BluetoothGattCharacteristic characteristic = getSupport() + .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE); + + List commandList = new ArrayList<>(); + for (int i = 0; i < 0xff; ++i) { + NotificationCommand cmd = new NotificationCommand(); + cmd.setServiceType(notificationType); + cmd.setExtendedServiceType(extendedNotificationType); + cmd.setCurrentPiece((byte) (i + 1)); + + int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH; + if (reserveSpaceForExtended) maxPayloadLength -= 1; + maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position()); + if (maxPayloadLength == 0) break; + + byte[] payload = new byte[maxPayloadLength]; + buffer.get(payload); + cmd.setPayload(payload); + + commandList.add(cmd); + } + + for (NotificationCommand cmd : commandList) { + cmd.setTotalPieces((byte) commandList.size()); + builder.write(characteristic, cmd.serialize()); + } + + if (isSelfQueue()) + getSupport().performConnected(builder.getTransaction()); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_NOTIFICATION; + } + + @Override + public boolean expectsResponse() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java new file mode 100644 index 000000000..bd572f62a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java @@ -0,0 +1,67 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class SendCallNotificationRequest extends AbstractSendNotificationRequest { + public SendCallNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + private CallSpec callNotification; + + public CallSpec getCallNotification() { + return callNotification; + } + + public void setCallNotification(CallSpec callNotification) { + this.callNotification = callNotification; + } + + @Override + protected String getMessage() { + String message = ""; + if (callNotification.number != null) { + message = callNotification.number; + } + + if (callNotification.name != null) { + if (message.length() > 0) { + message += " - "; + } + message += callNotification.name; + } + + return message; + } + + @Override + protected byte getNotificationType() { + return NotificationCommand.SERVICE_TYPE_CALL; + } + + @Override + protected byte getExtendedNotificationType() { + return 0; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java new file mode 100644 index 000000000..98405d0bc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java @@ -0,0 +1,111 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class SendNotificationRequest extends AbstractSendNotificationRequest { + NotificationSpec notification; + + public SendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + protected byte getNotificationType() { + switch (notification.type) { + case GENERIC_PHONE: + return NotificationCommand.SERVICE_TYPE_CALL; + case GENERIC_SMS: + case GENERIC_EMAIL: + default: + return NotificationCommand.SERVICE_TYPE_TEXT; + case WECHAT: + return NotificationCommand.SERVICE_TYPE_WECHAT; + case FACEBOOK: + case FACEBOOK_MESSENGER: + case TWITTER: + case LINKEDIN: + case WHATSAPP: + case LINE: + case KAKAO_TALK: + return NotificationCommand.SERVICE_TYPE_EXTENDED; + } + } + + @Override + protected byte getExtendedNotificationType() { + switch (notification.type) { + case GENERIC_PHONE: + case GENERIC_SMS: + case GENERIC_EMAIL: + default: + case WECHAT: + return 0; + case FACEBOOK: + case FACEBOOK_MESSENGER: + return NotificationCommand.EXTENDED_SERVICE_TYPE_FACEBOOK; + case TWITTER: + return NotificationCommand.EXTENDED_SERVICE_TYPE_TWITTER; + case LINKEDIN: + return NotificationCommand.EXTENDED_SERVICE_TYPE_LINKEDIN; + case WHATSAPP: + return NotificationCommand.EXTENDED_SERVICE_TYPE_WHATSAPP; + case LINE: + return NotificationCommand.EXTENDED_SERVICE_TYPE_LINE; + case KAKAO_TALK: + return NotificationCommand.EXTENDED_SERVICE_TYPE_KAKAOTALK; + } + } + + public NotificationSpec getNotification() { + return notification; + } + + public void setNotification(NotificationSpec notification) { + this.notification = notification; + } + + @Override + protected String getMessage() { + // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.id115.SendNotificationOperation + String message = ""; + + if (notification.phoneNumber != null) { + message += notification.phoneNumber + ": "; + } + + if (notification.sender != null) { + message += notification.sender + " - "; + } else if (notification.title != null) { + message += notification.title + " - "; + } else if (notification.subject != null) { + message += notification.subject + " - "; + } + + if (notification.body != null) { + message += notification.body; + } + + return message; + } +}