1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-18 12:16:36 +02:00

ID115: use SendNotificationOperation for message and call notifications

This commit is contained in:
Vadim Kaushan 2018-07-29 21:25:41 +03:00
parent 4ee1e6cfca
commit caabe0ed0a
3 changed files with 283 additions and 165 deletions

View File

@ -0,0 +1,80 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.widget.Toast;
import java.io.IOException;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public abstract class AbstractID115Operation extends AbstractBTLEOperation<ID115Support> {
protected BluetoothGattCharacteristic controlCharacteristic = null;
protected BluetoothGattCharacteristic notifyCharacteristic = null;
protected AbstractID115Operation(ID115Support support) {
super(support);
if (isHealthOperation()) {
controlCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_HEALTH);
notifyCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_HEALTH);
} else {
controlCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_NORMAL);
notifyCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL);
}
}
@Override
protected void prePerform() throws IOException {
super.prePerform();
getDevice().setBusyTask("AbstractID115Operation starting..."); // mark as busy quickly to avoid interruptions from the outside
TransactionBuilder builder = performInitialized("disabling some notifications");
enableNotifications(builder, true);
builder.queue(getQueue());
}
@Override
protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null && getDevice().isConnected()) {
unsetBusy();
try {
TransactionBuilder builder = performInitialized("reenabling disabled notifications");
enableNotifications(builder, false);
builder.setGattCallback(null); // unset ourselves from being the queue's gatt callback
builder.queue(getQueue());
} catch (IOException ex) {
GB.toast(getContext(), "Error enabling ID115 notifications, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR, ex);
}
}
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
if (notifyCharacteristic.getUuid().equals(characteristicUUID)) {
handleResponse(characteristic.getValue());
return true;
} else {
return super.onCharacteristicChanged(gatt, characteristic);
}
}
void enableNotifications(TransactionBuilder builder, boolean enable) {
if (isHealthOperation()) {
builder.notify(getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_HEALTH), enable);
} else {
builder.notify(getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL), enable);
}
}
abstract boolean isHealthOperation();
abstract void handleResponse(byte[] data);
}

View File

@ -37,7 +37,6 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(ID115Support.class);
public BluetoothGattCharacteristic normalWriteCharacteristic = null;
public BluetoothGattCharacteristic normalNotifyCharacteristic = null;
public BluetoothGattCharacteristic healthWriteCharacteristic = null;
byte[] currentNotificationBuffer;
@ -55,14 +54,10 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
normalWriteCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_NORMAL);
normalNotifyCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL);
healthWriteCharacteristic = getCharacteristic(ID115Constants.UUID_CHARACTERISTIC_WRITE_HEALTH);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setGattCallback(this);
builder.notify(normalNotifyCharacteristic, true);
setTime(builder)
.setWrist(builder)
.setScreenOrientation(builder)
@ -79,7 +74,11 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
@Override
public void onNotification(NotificationSpec notificationSpec) {
sendMessageNotification(notificationSpec);
try {
new SendNotificationOperation(this, notificationSpec).perform();
} catch (IOException ex) {
LOG.error("Unable to send ID115 notification", ex);
}
}
@Override
@ -106,7 +105,11 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
@Override
public void onSetCallState(CallSpec callSpec) {
if (callSpec.command == CallSpec.CALL_INCOMING) {
sendCallNotification(callSpec);
try {
new SendNotificationOperation(this, callSpec).perform();
} catch (IOException ex) {
LOG.error("Unable to send ID115 notification", ex);
}
} else {
sendStopCallNotification();
}
@ -241,40 +244,6 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (!characteristicUUID.equals(ID115Constants.UUID_CHARACTERISTIC_NOTIFY_NORMAL)) {
return false;
}
if (data.length < 2) {
LOG.warn("short GATT response");
return false;
}
if (data[0] == ID115Constants.CMD_ID_NOTIFY) {
if (data.length < 4) {
LOG.warn("short GATT response for NOTIFY");
return false;
}
if (data[1] == currentNotificationType) {
if (data[3] == currentNotificationIndex) {
if (currentNotificationIndex != currentNotificationSize) {
sendNotificationChunk(currentNotificationIndex + 1);
return true;
}
}
}
}
return false;
}
private void setInitialized(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
}
@ -359,23 +328,6 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
return this;
}
void sendCallNotification(CallSpec callSpec) {
String number = "";
if (callSpec.number != null) {
number = callSpec.number;
}
String name = "";
if (callSpec.name != null) {
name = callSpec.name;
}
currentNotificationBuffer = encodeCallNotification(name, number);
currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_CALL;
sendNotificationChunk(1);
}
void sendStopCallNotification() {
try {
TransactionBuilder builder = performInitialized("stop_call_notification");
@ -389,111 +341,4 @@ public class ID115Support extends AbstractBTLEDeviceSupport {
LOG.warn("Unable to stop call notification", e);
}
}
void sendMessageNotification(NotificationSpec notificationSpec) {
String phone = "";
if (notificationSpec.phoneNumber != null) {
phone = notificationSpec.phoneNumber;
}
String title = "";
if (notificationSpec.sender != null) {
title = notificationSpec.sender;
} else if (notificationSpec.title != null) {
title = notificationSpec.title;
} else if (notificationSpec.subject != null) {
title = notificationSpec.subject;
}
String text = "";
if (notificationSpec.body != null) {
text = notificationSpec.body;
}
currentNotificationBuffer = encodeMessageNotification(notificationSpec.type, title, phone, text);
currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_MSG;
sendNotificationChunk(1);
}
void sendNotificationChunk(int chunkIndex) {
currentNotificationIndex = chunkIndex;
int offset = (chunkIndex - 1) * 16;
int tailSize = currentNotificationBuffer.length - offset;
int chunkSize = (tailSize > 16)? 16 : tailSize;
byte raw[] = new byte[16];
System.arraycopy(currentNotificationBuffer, offset, raw, 0, chunkSize);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(ID115Constants.CMD_ID_NOTIFY);
outputStream.write(currentNotificationType);
outputStream.write((byte)currentNotificationSize);
outputStream.write((byte)currentNotificationIndex);
outputStream.write(raw);
byte cmd[] = outputStream.toByteArray();
TransactionBuilder builder = performInitialized("notification");
builder.write(normalWriteCharacteristic, cmd);
performConnected(builder.getTransaction());
} catch (IOException e) {
LOG.warn("Unable to send notification chunk", e);
}
}
byte[] encodeCallNotification(String name, String phone) {
if (name.length() > 20) {
name = name.substring(0, 20);
}
if (phone.length() > 20) {
phone = phone.substring(0, 20);
}
byte[] name_bytes = name.getBytes(StandardCharsets.UTF_8);
byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write((byte) phone_bytes.length);
outputStream.write((byte) name_bytes.length);
outputStream.write(phone_bytes);
outputStream.write(name_bytes);
return outputStream.toByteArray();
} catch (IOException e) {
return null;
}
}
byte[] encodeMessageNotification(NotificationType type, String title, String phone, String text) {
if (title.length() > 20) {
title = title.substring(0, 20);
}
if (phone.length() > 20) {
phone = phone.substring(0, 20);
}
if (text.length() > 20) {
text = text.substring(0, 20);
}
byte[] title_bytes = title.getBytes(StandardCharsets.UTF_8);
byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
byte[] text_bytes = text.getBytes(StandardCharsets.UTF_8);
byte nativeType = ID115Constants.getNotificationType(type);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(nativeType);
outputStream.write((byte) text_bytes.length);
outputStream.write((byte) phone_bytes.length);
outputStream.write((byte) title_bytes.length);
outputStream.write(phone_bytes);
outputStream.write(title_bytes);
outputStream.write(text_bytes);
return outputStream.toByteArray();
} catch (IOException e) {
return null;
}
}
}

View File

@ -0,0 +1,193 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.id115;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.devices.id115.ID115Constants;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class SendNotificationOperation extends AbstractID115Operation {
private static final Logger LOG = LoggerFactory.getLogger(SendNotificationOperation.class);
byte[] currentNotificationBuffer;
int currentNotificationSize;
int currentNotificationIndex;
byte currentNotificationType;
SendNotificationOperation(ID115Support support, NotificationSpec notificationSpec)
{
super(support);
String phone = "";
if (notificationSpec.phoneNumber != null) {
phone = notificationSpec.phoneNumber;
}
String title = "";
if (notificationSpec.sender != null) {
title = notificationSpec.sender;
} else if (notificationSpec.title != null) {
title = notificationSpec.title;
} else if (notificationSpec.subject != null) {
title = notificationSpec.subject;
}
String text = "";
if (notificationSpec.body != null) {
text = notificationSpec.body;
}
currentNotificationBuffer = encodeMessageNotification(notificationSpec.type, title, phone, text);
currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_MSG;
}
SendNotificationOperation(ID115Support support, CallSpec callSpec)
{
super(support);
String number = "";
if (callSpec.number != null) {
number = callSpec.number;
}
String name = "";
if (callSpec.name != null) {
name = callSpec.name;
}
currentNotificationBuffer = encodeCallNotification(name, number);
currentNotificationSize = (currentNotificationBuffer.length + 15) / 16;
currentNotificationType = ID115Constants.CMD_KEY_NOTIFY_CALL;
}
@Override
boolean isHealthOperation() {
return false;
}
@Override
protected void doPerform() throws IOException {
sendNotificationChunk(1);
}
void sendNotificationChunk(int chunkIndex) throws IOException {
currentNotificationIndex = chunkIndex;
int offset = (chunkIndex - 1) * 16;
int tailSize = currentNotificationBuffer.length - offset;
int chunkSize = (tailSize > 16)? 16 : tailSize;
byte raw[] = new byte[16];
System.arraycopy(currentNotificationBuffer, offset, raw, 0, chunkSize);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(ID115Constants.CMD_ID_NOTIFY);
outputStream.write(currentNotificationType);
outputStream.write((byte)currentNotificationSize);
outputStream.write((byte)currentNotificationIndex);
outputStream.write(raw);
byte cmd[] = outputStream.toByteArray();
TransactionBuilder builder = performInitialized("send notification chunk");
builder.write(controlCharacteristic, cmd);
builder.queue(getQueue());
}
void handleResponse(byte[] data) {
if (!isOperationRunning()) {
LOG.error("ignoring notification because operation is not running. Data length: " + data.length);
getSupport().logMessageContent(data);
return;
}
if (data.length < 2) {
LOG.warn("short GATT response");
return;
}
if (data[0] == ID115Constants.CMD_ID_NOTIFY) {
if (data.length < 4) {
LOG.warn("short GATT response for NOTIFY");
return;
}
if (data[1] == currentNotificationType) {
if (data[3] == currentNotificationIndex) {
if (currentNotificationIndex != currentNotificationSize) {
try {
sendNotificationChunk(currentNotificationIndex + 1);
} catch (IOException ex) {
GB.toast(getContext(), "Error sending ID115 notification, you may need to connect and disconnect", Toast.LENGTH_LONG, GB.ERROR, ex);
}
} else {
LOG.info("Notification transfer has finished.");
operationFinished();
}
}
}
}
}
byte[] encodeCallNotification(String name, String phone) {
if (name.length() > 20) {
name = name.substring(0, 20);
}
if (phone.length() > 20) {
phone = phone.substring(0, 20);
}
byte[] name_bytes = name.getBytes(StandardCharsets.UTF_8);
byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write((byte) phone_bytes.length);
outputStream.write((byte) name_bytes.length);
outputStream.write(phone_bytes);
outputStream.write(name_bytes);
return outputStream.toByteArray();
} catch (IOException e) {
return null;
}
}
byte[] encodeMessageNotification(NotificationType type, String title, String phone, String text) {
if (title.length() > 20) {
title = title.substring(0, 20);
}
if (phone.length() > 20) {
phone = phone.substring(0, 20);
}
if (text.length() > 20) {
text = text.substring(0, 20);
}
byte[] title_bytes = title.getBytes(StandardCharsets.UTF_8);
byte[] phone_bytes = phone.getBytes(StandardCharsets.UTF_8);
byte[] text_bytes = text.getBytes(StandardCharsets.UTF_8);
byte nativeType = ID115Constants.getNotificationType(type);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(nativeType);
outputStream.write((byte) text_bytes.length);
outputStream.write((byte) phone_bytes.length);
outputStream.write((byte) title_bytes.length);
outputStream.write(phone_bytes);
outputStream.write(title_bytes);
outputStream.write(text_bytes);
return outputStream.toByteArray();
} catch (IOException e) {
return null;
}
}
}