mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-16 10:00:08 +02:00
488 lines
27 KiB
Java
488 lines
27 KiB
Java
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
|
|
|
|
import android.location.Location;
|
|
|
|
import com.google.protobuf.InvalidProtocolBufferException;
|
|
|
|
import org.apache.commons.lang3.ArrayUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
|
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
|
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCalendarService;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDataTransferService;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDeviceStatus;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiFindMyWatch;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiHttpService;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmartProto;
|
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmsNotification;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.DataTransferHandler;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHandler;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
|
|
|
|
public class ProtocolBufferHandler implements MessageHandler {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(ProtocolBufferHandler.class);
|
|
private final GarminSupport deviceSupport;
|
|
private final Map<Integer, ProtobufFragment> chunkedFragmentsMap;
|
|
private final int maxChunkSize = 375; //tested on VĂvomove Style
|
|
private int lastProtobufRequestId;
|
|
private final HttpHandler httpHandler;
|
|
private final DataTransferHandler dataTransferHandler;
|
|
|
|
private final Map<GdiSmsNotification.SmsNotificationService.CannedListType, String[]> cannedListTypeMap = new HashMap<>();
|
|
|
|
public ProtocolBufferHandler(GarminSupport deviceSupport) {
|
|
this.deviceSupport = deviceSupport;
|
|
chunkedFragmentsMap = new HashMap<>();
|
|
httpHandler = new HttpHandler(deviceSupport);
|
|
dataTransferHandler = new DataTransferHandler();
|
|
}
|
|
|
|
private int getNextProtobufRequestId() {
|
|
lastProtobufRequestId = (lastProtobufRequestId + 1) % 65536;
|
|
return lastProtobufRequestId;
|
|
}
|
|
|
|
public ProtobufMessage handle(GFDIMessage protobufMessage) {
|
|
if (protobufMessage instanceof ProtobufMessage) {
|
|
return processIncoming((ProtobufMessage) protobufMessage);
|
|
} else if (protobufMessage instanceof ProtobufStatusMessage) {
|
|
return processIncoming((ProtobufStatusMessage) protobufMessage);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private ProtobufMessage processIncoming(ProtobufMessage message) {
|
|
ProtobufFragment protobufFragment = processChunkedMessage(message);
|
|
|
|
if (protobufFragment.isComplete()) { //message is now complete
|
|
LOG.info("Received protobuf message #{}, {}B: {}", message.getRequestId(), protobufFragment.totalLength, GB.hexdump(protobufFragment.fragmentBytes, 0, protobufFragment.totalLength));
|
|
|
|
final GdiSmartProto.Smart smart;
|
|
try {
|
|
smart = GdiSmartProto.Smart.parseFrom(protobufFragment.fragmentBytes);
|
|
} catch (InvalidProtocolBufferException e) {
|
|
LOG.error("Failed to parse protobuf message ({}): {}", e.getLocalizedMessage(), GB.hexdump(protobufFragment.fragmentBytes));
|
|
return null;
|
|
}
|
|
boolean processed = false;
|
|
if (smart.hasCoreService()) { //TODO: unify request and response???
|
|
return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId());
|
|
}
|
|
if (smart.hasCalendarService()) {
|
|
return prepareProtobufResponse(processProtobufCalendarRequest(smart.getCalendarService()), message.getRequestId());
|
|
}
|
|
if (smart.hasSmsNotificationService()) {
|
|
return prepareProtobufResponse(processProtobufSmsNotificationMessage(smart.getSmsNotificationService()), message.getRequestId());
|
|
}
|
|
if (smart.hasHttpService()) {
|
|
final GdiHttpService.HttpService response = httpHandler.handle(smart.getHttpService());
|
|
if (response == null) {
|
|
return null;
|
|
}
|
|
return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setHttpService(response).build(), message.getRequestId());
|
|
}
|
|
if (smart.hasDataTransferService()) {
|
|
final GdiDataTransferService.DataTransferService response = dataTransferHandler.handle(smart.getDataTransferService(), message.getRequestId());
|
|
if (response == null) {
|
|
return null;
|
|
}
|
|
return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setDataTransferService(response).build(), message.getRequestId());
|
|
}
|
|
if (smart.hasDeviceStatusService()) {
|
|
processed = true;
|
|
processProtobufDeviceStatusResponse(smart.getDeviceStatusService());
|
|
}
|
|
if (smart.hasFindMyWatchService()) {
|
|
processed = true;
|
|
processProtobufFindMyWatchResponse(smart.getFindMyWatchService());
|
|
}
|
|
if (!processed) {
|
|
LOG.warn("Unknown protobuf request: {}", smart);
|
|
message.setStatusMessage(new ProtobufStatusMessage(message.getMessageType(), GFDIMessage.Status.ACK, message.getRequestId(), message.getDataOffset(), ProtobufStatusMessage.ProtobufChunkStatus.DISCARDED, ProtobufStatusMessage.ProtobufStatusCode.UNKNOWN_REQUEST_ID));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private ProtobufMessage processIncoming(ProtobufStatusMessage statusMessage) {
|
|
LOG.info("Processing protobuf status message #{}@{}: status={}, error={}", statusMessage.getRequestId(), statusMessage.getDataOffset(), statusMessage.getProtobufChunkStatus(), statusMessage.getProtobufStatusCode());
|
|
//TODO: check status and react accordingly, right now we blindly proceed to next chunk
|
|
if (statusMessage.isOK()) {
|
|
DataTransferHandler.onDataChunkSuccessfullyReceived(statusMessage.getRequestId());
|
|
}
|
|
if (chunkedFragmentsMap.containsKey(statusMessage.getRequestId()) && statusMessage.isOK()) {
|
|
final ProtobufFragment protobufFragment = chunkedFragmentsMap.get(statusMessage.getRequestId());
|
|
LOG.debug("Protobuf message #{} found in queue: {}", statusMessage.getRequestId(), GB.hexdump(protobufFragment.fragmentBytes));
|
|
|
|
if (protobufFragment.totalLength <= (statusMessage.getDataOffset() + maxChunkSize)) {
|
|
chunkedFragmentsMap.remove(protobufFragment);
|
|
}
|
|
|
|
return protobufFragment.getNextChunk(statusMessage);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private ProtobufFragment processChunkedMessage(ProtobufMessage message) {
|
|
if (message.isComplete()) //comment this out if for any reason also smaller messages should end up in the map
|
|
return new ProtobufFragment(message.getMessageBytes());
|
|
|
|
if (message.getDataOffset() == 0) { //store new messages beginning at 0, overwrite old messages
|
|
chunkedFragmentsMap.put(message.getRequestId(), new ProtobufFragment(message));
|
|
LOG.info("Protobuf request put in queue: #{} , {}", message.getRequestId(), GB.hexdump(message.getMessageBytes()));
|
|
} else {
|
|
if (chunkedFragmentsMap.containsKey(message.getRequestId())) {
|
|
ProtobufFragment oldFragment = chunkedFragmentsMap.get(message.getRequestId());
|
|
chunkedFragmentsMap.put(message.getRequestId(),
|
|
new ProtobufFragment(oldFragment, message));
|
|
}
|
|
}
|
|
return chunkedFragmentsMap.get(message.getRequestId());
|
|
}
|
|
|
|
private GdiSmartProto.Smart processProtobufCalendarRequest(GdiCalendarService.CalendarService calendarService) {
|
|
if (calendarService.hasCalendarRequest()) {
|
|
GdiCalendarService.CalendarService.CalendarServiceRequest calendarServiceRequest = calendarService.getCalendarRequest();
|
|
|
|
CalendarManager upcomingEvents = new CalendarManager(deviceSupport.getContext(), deviceSupport.getDevice().getAddress());
|
|
List<CalendarEvent> mEvents = upcomingEvents.getCalendarEventList();
|
|
List<GdiCalendarService.CalendarService.CalendarEvent> watchEvents = new ArrayList<>();
|
|
|
|
for (CalendarEvent mEvt : mEvents) {
|
|
if (mEvt.getEndSeconds() < calendarServiceRequest.getBegin() ||
|
|
mEvt.getBeginSeconds() > calendarServiceRequest.getEnd()) {
|
|
LOG.debug("CalendarService Skipping event {} that is out of requested time range", mEvt.getTitle());
|
|
continue;
|
|
}
|
|
if (!calendarServiceRequest.getIncludeAllDay() && mEvt.isAllDay()) {
|
|
LOG.debug("CalendarService Skipping event {} that is AllDay", mEvt.getTitle());
|
|
continue;
|
|
}
|
|
|
|
if (watchEvents.size() >= calendarServiceRequest.getMaxEvents() * 2) { //NOTE: Tested with values higher than double of the reported max without issues
|
|
LOG.debug("Reached the maximum number of events supported by the watch");
|
|
break;
|
|
}
|
|
|
|
final GdiCalendarService.CalendarService.CalendarEvent.Builder event = GdiCalendarService.CalendarService.CalendarEvent.newBuilder()
|
|
.setTitle(mEvt.getTitle().substring(0, Math.min(mEvt.getTitle().length(), calendarServiceRequest.getMaxTitleLength())))
|
|
.setAllDay(mEvt.isAllDay())
|
|
.setStartDate(mEvt.getBeginSeconds())
|
|
.setEndDate(mEvt.getEndSeconds());
|
|
|
|
if (calendarServiceRequest.getIncludeLocation() && mEvt.getLocation() != null) {
|
|
event.setLocation(mEvt.getLocation().substring(0, Math.min(mEvt.getLocation().length(), calendarServiceRequest.getMaxLocationLength())));
|
|
}
|
|
|
|
if (calendarServiceRequest.getIncludeDescription() && mEvt.getDescription() != null) {
|
|
event.setDescription(mEvt.getDescription().substring(0, Math.min(mEvt.getDescription().length(), calendarServiceRequest.getMaxDescriptionLength())));
|
|
}
|
|
if (calendarServiceRequest.getIncludeOrganizer() && mEvt.getOrganizer() != null) {
|
|
event.setDescription(mEvt.getOrganizer().substring(0, Math.min(mEvt.getOrganizer().length(), calendarServiceRequest.getMaxOrganizerLength())));
|
|
}
|
|
watchEvents.add(event.build());
|
|
}
|
|
|
|
LOG.debug("CalendarService Sending {} events to watch", watchEvents.size());
|
|
return GdiSmartProto.Smart.newBuilder().setCalendarService(
|
|
GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
|
|
GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
|
|
.addAllCalendarEvent(watchEvents)
|
|
.setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.OK)
|
|
)
|
|
).build();
|
|
}
|
|
LOG.warn("Unknown CalendarService request: {}", calendarService);
|
|
return GdiSmartProto.Smart.newBuilder().setCalendarService(
|
|
GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
|
|
GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
|
|
.setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.UNKNOWN_RESPONSE_STATUS)
|
|
)
|
|
).build();
|
|
}
|
|
|
|
private void processProtobufDeviceStatusResponse(GdiDeviceStatus.DeviceStatusService deviceStatusService) {
|
|
if (deviceStatusService.hasRemoteDeviceBatteryStatusResponse()) {
|
|
final GdiDeviceStatus.DeviceStatusService.RemoteDeviceBatteryStatusResponse batteryStatusResponse = deviceStatusService.getRemoteDeviceBatteryStatusResponse();
|
|
final int batteryLevel = batteryStatusResponse.getCurrentBatteryLevel();
|
|
LOG.info("Received remote battery status {}: level={}", batteryStatusResponse.getStatus(), batteryLevel);
|
|
final GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
|
|
batteryEvent.level = (short) batteryLevel;
|
|
deviceSupport.evaluateGBDeviceEvent(batteryEvent);
|
|
return;
|
|
}
|
|
if (deviceStatusService.hasActivityStatusResponse()) {
|
|
final GdiDeviceStatus.DeviceStatusService.ActivityStatusResponse activityStatusResponse = deviceStatusService.getActivityStatusResponse();
|
|
LOG.info("Received activity status: {}", activityStatusResponse.getStatus());
|
|
return;
|
|
}
|
|
LOG.warn("Unknown DeviceStatusService response: {}", deviceStatusService);
|
|
}
|
|
|
|
private GdiSmartProto.Smart processProtobufCoreRequest(GdiCore.CoreService coreService) {
|
|
if (coreService.hasSyncResponse()) {
|
|
final GdiCore.CoreService.SyncResponse syncResponse = coreService.getSyncResponse();
|
|
LOG.info("Received sync status: {}", syncResponse.getStatus());
|
|
return null;
|
|
}
|
|
|
|
if (coreService.hasGetLocationRequest()) {
|
|
LOG.info("Got location request");
|
|
final Location location = new CurrentPosition().getLastKnownLocation();
|
|
final GdiCore.CoreService.GetLocationResponse.Builder response = GdiCore.CoreService.GetLocationResponse.newBuilder();
|
|
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
|
|
response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.NO_VALID_LOCATION);
|
|
} else {
|
|
response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.OK)
|
|
.setLocationData(GarminUtils.toLocationData(location, GdiCore.CoreService.DataType.GENERAL_LOCATION));
|
|
}
|
|
return GdiSmartProto.Smart.newBuilder().setCoreService(
|
|
GdiCore.CoreService.newBuilder().setGetLocationResponse(response)).build();
|
|
}
|
|
|
|
if (coreService.hasLocationUpdatedSetEnabledRequest()) {
|
|
final GdiCore.CoreService.LocationUpdatedSetEnabledRequest locationUpdatedSetEnabledRequest = coreService.getLocationUpdatedSetEnabledRequest();
|
|
|
|
LOG.info("Received locationUpdatedSetEnabledRequest status: {}", locationUpdatedSetEnabledRequest.getEnabled());
|
|
|
|
GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Builder response = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.newBuilder()
|
|
.setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.OK);
|
|
|
|
final boolean sendGpsPref = deviceSupport.getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
|
|
|
|
GdiCore.CoreService.Request realtimeRequest = null;
|
|
|
|
if (locationUpdatedSetEnabledRequest.getEnabled()) {
|
|
for (final GdiCore.CoreService.Request request : locationUpdatedSetEnabledRequest.getRequestsList()) {
|
|
final GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus requestedStatus;
|
|
if (GdiCore.CoreService.DataType.REALTIME_TRACKING.equals(request.getRequested())) {
|
|
realtimeRequest = request;
|
|
if (sendGpsPref) {
|
|
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK;
|
|
} else {
|
|
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
|
|
}
|
|
} else {
|
|
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
|
|
}
|
|
|
|
response.addRequests(
|
|
GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder()
|
|
.setRequested(request.getRequested())
|
|
.setStatus(requestedStatus)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (sendGpsPref) {
|
|
if (realtimeRequest != null) {
|
|
GBLocationService.start(
|
|
deviceSupport.getContext(),
|
|
deviceSupport.getDevice(),
|
|
GBLocationProviderType.GPS,
|
|
1000 // TODO from realtimeRequest
|
|
);
|
|
} else {
|
|
GBLocationService.stop(deviceSupport.getContext(), deviceSupport.getDevice());
|
|
}
|
|
}
|
|
|
|
return GdiSmartProto.Smart.newBuilder().setCoreService(
|
|
GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build();
|
|
}
|
|
|
|
LOG.warn("Unknown CoreService request: {}", coreService);
|
|
return null;
|
|
}
|
|
|
|
private GdiSmartProto.Smart processProtobufSmsNotificationMessage(GdiSmsNotification.SmsNotificationService smsNotificationService) {
|
|
if (smsNotificationService.hasSmsCannedListRequest()) {
|
|
LOG.debug("Got request for sms canned list");
|
|
|
|
// Mark canned messages as supported
|
|
deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, true));
|
|
|
|
if (this.cannedListTypeMap.isEmpty()) {
|
|
List<GdiSmsNotification.SmsNotificationService.CannedListType> requestedTypes = smsNotificationService.getSmsCannedListRequest().getRequestedTypesList();
|
|
for (GdiSmsNotification.SmsNotificationService.CannedListType type :
|
|
requestedTypes) {
|
|
if (GdiSmsNotification.SmsNotificationService.CannedListType.SMS_MESSAGE_RESPONSE.equals(type)) {
|
|
final ArrayList<String> messages = new ArrayList<>();
|
|
for (int i = 1; i <= 16; i++) {
|
|
String message = deviceSupport.getDevicePrefs().getString("canned_reply_" + i, null);
|
|
if (message != null && !message.isEmpty()) {
|
|
messages.add(message);
|
|
}
|
|
}
|
|
if (!messages.isEmpty())
|
|
this.cannedListTypeMap.put(type, messages.toArray(new String[0]));
|
|
} else if (GdiSmsNotification.SmsNotificationService.CannedListType.PHONE_CALL_RESPONSE.equals(type)) {
|
|
final ArrayList<String> messages = new ArrayList<>();
|
|
for (int i = 1; i <= 16; i++) {
|
|
String message = deviceSupport.getDevicePrefs().getString("canned_message_dismisscall_" + i, null);
|
|
if (message != null && !message.isEmpty()) {
|
|
messages.add(message);
|
|
}
|
|
}
|
|
if (!messages.isEmpty())
|
|
this.cannedListTypeMap.put(type, messages.toArray(new String[0]));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
List<GdiSmsNotification.SmsNotificationService.CannedListType> requestedTypes = smsNotificationService.getSmsCannedListRequest().getRequestedTypesList();
|
|
|
|
GdiSmsNotification.SmsNotificationService.SmsCannedListResponse.Builder builder = GdiSmsNotification.SmsNotificationService.SmsCannedListResponse.newBuilder()
|
|
.setStatus(GdiSmsNotification.SmsNotificationService.ResponseStatus.SUCCESS);
|
|
for (GdiSmsNotification.SmsNotificationService.CannedListType requestedType : requestedTypes) {
|
|
if (this.cannedListTypeMap.containsKey(requestedType)) {
|
|
builder.addLists(GdiSmsNotification.SmsNotificationService.SmsCannedList.newBuilder()
|
|
.addAllResponse(Arrays.asList(Objects.requireNonNull(this.cannedListTypeMap.get(requestedType))))
|
|
.setType(requestedType)
|
|
);
|
|
} else {
|
|
builder.setStatus(GdiSmsNotification.SmsNotificationService.ResponseStatus.GENERIC_ERROR);
|
|
LOG.info("Missing canned messages data for type {}", requestedType);
|
|
}
|
|
}
|
|
|
|
return GdiSmartProto.Smart.newBuilder().setSmsNotificationService(GdiSmsNotification.SmsNotificationService.newBuilder().setSmsCannedListResponse(builder)).build();
|
|
} else {
|
|
LOG.warn("Protobuf smsNotificationService request not implemented: {}", smsNotificationService);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void processProtobufFindMyWatchResponse(GdiFindMyWatch.FindMyWatchService findMyWatchService) {
|
|
if (findMyWatchService.hasCancelRequest()) {
|
|
LOG.info("Watch found");
|
|
}
|
|
if (findMyWatchService.hasCancelResponse() || findMyWatchService.hasFindResponse()) {
|
|
LOG.debug("Received findMyWatch response");
|
|
}
|
|
LOG.warn("Unknown FindMyWatchService response: {}", findMyWatchService);
|
|
}
|
|
|
|
public ProtobufMessage prepareProtobufRequest(GdiSmartProto.Smart protobufPayload) {
|
|
if (null == protobufPayload)
|
|
return null;
|
|
final int requestId = getNextProtobufRequestId();
|
|
return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_REQUEST, requestId);
|
|
}
|
|
|
|
private ProtobufMessage prepareProtobufResponse(GdiSmartProto.Smart protobufPayload, int requestId) {
|
|
if (null == protobufPayload)
|
|
return null;
|
|
return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_RESPONSE, requestId);
|
|
}
|
|
|
|
private ProtobufMessage prepareProtobufMessage(byte[] bytes, GFDIMessage.GarminMessage garminMessage, int requestId) {
|
|
if (bytes == null || bytes.length == 0)
|
|
return null;
|
|
LOG.info("Preparing protobuf message. Type{}, #{}, {}B: {}", garminMessage, requestId, bytes.length, GB.hexdump(bytes, 0, bytes.length));
|
|
|
|
if (bytes.length > maxChunkSize) {
|
|
chunkedFragmentsMap.put(requestId, new ProtobufFragment(bytes));
|
|
return new ProtobufMessage(garminMessage,
|
|
requestId,
|
|
0,
|
|
bytes.length,
|
|
maxChunkSize,
|
|
ArrayUtils.subarray(bytes, 0, maxChunkSize));
|
|
}
|
|
return new ProtobufMessage(garminMessage, requestId, 0, bytes.length, bytes.length, bytes);
|
|
}
|
|
|
|
public ProtobufMessage setCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
|
final GdiSmsNotification.SmsNotificationService.CannedListType cannedListType;
|
|
switch (cannedMessagesSpec.type) {
|
|
case CannedMessagesSpec.TYPE_REJECTEDCALLS:
|
|
cannedListType = GdiSmsNotification.SmsNotificationService.CannedListType.PHONE_CALL_RESPONSE;
|
|
break;
|
|
case CannedMessagesSpec.TYPE_GENERIC:
|
|
case CannedMessagesSpec.TYPE_NEWSMS:
|
|
cannedListType = GdiSmsNotification.SmsNotificationService.CannedListType.SMS_MESSAGE_RESPONSE;
|
|
break;
|
|
default:
|
|
LOG.warn("Unknown canned messages type, ignoring.");
|
|
return null;
|
|
}
|
|
|
|
this.cannedListTypeMap.put(cannedListType, cannedMessagesSpec.cannedMessages);
|
|
|
|
GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder()
|
|
.setSmsNotificationService(GdiSmsNotification.SmsNotificationService.newBuilder()
|
|
.setSmsCannedListChangedNotification(
|
|
GdiSmsNotification.SmsNotificationService.SmsCannedListChangedNotification.newBuilder().addChangedType(cannedListType)
|
|
)
|
|
).build();
|
|
|
|
return prepareProtobufRequest(smart);
|
|
}
|
|
|
|
private class ProtobufFragment {
|
|
private final byte[] fragmentBytes;
|
|
private final int totalLength;
|
|
|
|
public ProtobufFragment(byte[] fragmentBytes) {
|
|
this.fragmentBytes = fragmentBytes;
|
|
this.totalLength = fragmentBytes.length;
|
|
}
|
|
|
|
public ProtobufFragment(ProtobufMessage message) {
|
|
if (message.getDataOffset() != 0)
|
|
throw new IllegalArgumentException("Cannot create fragment if message is not the first of the sequence");
|
|
this.fragmentBytes = message.getMessageBytes();
|
|
this.totalLength = message.getTotalProtobufLength();
|
|
}
|
|
|
|
public ProtobufFragment(ProtobufFragment existing, ProtobufMessage toMerge) {
|
|
if (toMerge.getDataOffset() != existing.fragmentBytes.length)
|
|
throw new IllegalArgumentException("Cannot merge fragment: incoming message has different offset than needed");
|
|
this.fragmentBytes = ArrayUtils.addAll(existing.fragmentBytes, toMerge.getMessageBytes());
|
|
this.totalLength = existing.totalLength;
|
|
}
|
|
|
|
public ProtobufMessage getNextChunk(ProtobufStatusMessage protobufStatusMessage) {
|
|
int start = protobufStatusMessage.getDataOffset() + maxChunkSize;
|
|
int length = Math.min(maxChunkSize, this.fragmentBytes.length - start);
|
|
|
|
return new ProtobufMessage(protobufStatusMessage.getMessageType(),
|
|
protobufStatusMessage.getRequestId(),
|
|
start,
|
|
this.totalLength,
|
|
length,
|
|
ArrayUtils.subarray(this.fragmentBytes, start, start + length));
|
|
}
|
|
|
|
public boolean isComplete() {
|
|
return totalLength == fragmentBytes.length;
|
|
}
|
|
}
|
|
|
|
|
|
}
|