diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java index 39df4ce33..c082cf603 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsScreen.java @@ -30,6 +30,7 @@ public enum DeviceSpecificSettingsScreen { DEVELOPER("pref_screen_developer", R.xml.devicesettings_root_developer), DISPLAY("pref_screen_display", R.xml.devicesettings_root_display), GENERIC("pref_screen_generic", R.xml.devicesettings_root_generic), + LOCATION("pref_screen_location", R.xml.devicesettings_root_location), NOTIFICATIONS("pref_screen_notifications", R.xml.devicesettings_root_notifications), DATE_TIME("pref_screen_date_time", R.xml.devicesettings_root_date_time), WORKOUT("pref_screen_workout", R.xml.devicesettings_root_workout), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java index 131260c2b..66a618217 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java @@ -39,6 +39,9 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { final List notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS); notifications.add(R.xml.devicesettings_send_app_notifications); + final List location = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.LOCATION); + location.add(R.xml.devicesettings_workout_send_gps_to_band); + final List connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION); connection.add(R.xml.devicesettings_high_mtu); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java index 55a3cf976..f80d3a9ec 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java @@ -2,6 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.location.Location; +import android.os.Build; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +28,8 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences; import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability; +import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager; +import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.LocationProviderType; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -33,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDeviceStatus; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiFindMyWatch; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmartProto; @@ -78,6 +83,8 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni private Timer musicStateTimer; private final List supportedFileTypeList = new ArrayList<>(); + private final List locationRequestsList = new ArrayList<>(); + public GarminSupport() { super(LOG); addSupportedService(CommunicatorV1.UUID_SERVICE_GARMIN_GFDI); @@ -94,8 +101,10 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni @Override public void dispose() { - super.dispose(); + LOG.info("Garmin dispose()"); + stopLocationUpdate(); stopMusicTimer(); + super.dispose(); } private void stopMusicTimer() { @@ -548,5 +557,69 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni return dir; } + public void processLocationUpdateRequest(final boolean enable, final List requestsList) { + if (enable) { + boolean useGPS = false; + for (GdiCore.CoreService.Request request: requestsList) { + if (GdiCore.CoreService.DataType.REALTIME_TRACKING.equals(request.getRequested())) { + useGPS = true; + break; + } + } + if (useGPS) + GBLocationManager.start(getContext(), this); + else + GBLocationManager.start(getContext(), this, LocationProviderType.NETWORK, 5*60*1000); + //TODO: spin up several listener according to the required precision and timeouts + this.locationRequestsList.clear(); + this.locationRequestsList.addAll(requestsList); + //OpenTracksController.startRecording(deviceSupport.getContext()); + } else { + stopLocationUpdate(); + } + } + + private void stopLocationUpdate() { + this.locationRequestsList.clear(); + GBLocationManager.stop(getContext(), this); + //OpenTracksController.stopRecording(deviceSupport.getContext()); + } + + @Override + public void onSetGpsLocation(final Location location) { + final GdiCore.CoreService.LatLon positionForWatch = GdiCore.CoreService.LatLon.newBuilder() + .setLat((int) ((location.getLatitude() * 2.147483648E9d) / 180.0d)) + .setLon((int) ((location.getLongitude() * 2.147483648E9d) / 180.0d)) + .build(); + + float vAccuracy = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vAccuracy = location.getVerticalAccuracyMeters(); + } + + GdiCore.CoreService.LocationUpdatedNotification.Builder locationUpdatedNotification = GdiCore.CoreService.LocationUpdatedNotification.newBuilder(); + + for (GdiCore.CoreService.Request req : this.locationRequestsList) { + locationUpdatedNotification.addLocationData( + GdiCore.CoreService.LocationData.newBuilder() + .setPosition(positionForWatch) + .setAltitude((float) location.getAltitude()) + .setTimestamp(GarminTimeUtils.javaMillisToGarminTimestamp(location.getTime())) + .setHAccuracy(location.getAccuracy()) + .setVAccuracy(vAccuracy) + .setPositionType(req.getRequested()) + .setBearing(location.getBearing()) + .setSpeed(location.getSpeed()) + ); + } + + final ProtobufMessage locationUpdatedNotificationRequest = protocolBufferHandler.prepareProtobufRequest( + GdiSmartProto.Smart.newBuilder().setCoreService( + GdiCore.CoreService.newBuilder(). + setLocationUpdatedNotification(locationUpdatedNotification) + ).build() + ); + sendOutgoingMessage(locationUpdatedNotificationRequest); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java new file mode 100644 index 000000000..42099de8f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminUtils.java @@ -0,0 +1,34 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin; + +import android.location.Location; +import android.os.Build; + +import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore; + +public final class GarminUtils { + private GarminUtils() { + // utility class + } + + public static GdiCore.CoreService.LocationData toLocationData(final Location location, final GdiCore.CoreService.DataType dataType) { + final GdiCore.CoreService.LatLon positionForWatch = GdiCore.CoreService.LatLon.newBuilder() + .setLat((int) ((location.getLatitude() * 2.147483648E9d) / 180.0d)) + .setLon((int) ((location.getLongitude() * 2.147483648E9d) / 180.0d)) + .build(); + float vAccuracy = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vAccuracy = location.getVerticalAccuracyMeters(); + } + + return GdiCore.CoreService.LocationData.newBuilder() + .setPosition(positionForWatch) + .setAltitude((float) location.getAltitude()) + .setTimestamp(GarminTimeUtils.javaMillisToGarminTimestamp(location.getTime())) + .setHAccuracy(location.getAccuracy()) + .setVAccuracy(vAccuracy) + .setPositionType(dataType) + .setBearing(location.getBearing()) + .setSpeed(location.getSpeed()) + .build(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java index 8b418ff88..e433e60e7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/ProtocolBufferHandler.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin; +import android.location.Location; + import com.google.protobuf.InvalidProtocolBufferException; import org.apache.commons.lang3.ArrayUtils; @@ -12,6 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCalendarService; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore; @@ -23,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHand 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; @@ -69,9 +73,7 @@ public class ProtocolBufferHandler implements MessageHandler { } boolean processed = false; if (smart.hasCoreService()) { //TODO: unify request and response??? - processed = true; - processProtobufCoreResponse(smart.getCoreService()); -// return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId()); + return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId()); } if (smart.hasCalendarService()) { return prepareProtobufResponse(processProtobufCalendarRequest(smart.getCalendarService()), message.getRequestId()); @@ -176,14 +178,6 @@ public class ProtocolBufferHandler implements MessageHandler { ).build(); } - private void processProtobufCoreResponse(GdiCore.CoreService coreService) { - if (coreService.hasSyncResponse()) { - final GdiCore.CoreService.SyncResponse syncResponse = coreService.getSyncResponse(); - LOG.info("Received sync status: {}", syncResponse.getStatus()); - } - LOG.warn("Unknown CoreService response: {}", coreService); - } - private void processProtobufDeviceStatusResponse(GdiDeviceStatus.DeviceStatusService deviceStatusService) { if (deviceStatusService.hasRemoteDeviceBatteryStatusResponse()) { final GdiDeviceStatus.DeviceStatusService.RemoteDeviceBatteryStatusResponse batteryStatusResponse = deviceStatusService.getRemoteDeviceBatteryStatusResponse(); @@ -202,32 +196,54 @@ public class ProtocolBufferHandler implements MessageHandler { LOG.warn("Unknown DeviceStatusService response: {}", deviceStatusService); } -// private GdiSmartProto.Smart processProtobufCoreRequest(GdiCore.CoreService coreService) { -// if (coreService.hasLocationUpdatedSetEnabledRequest()) { //TODO: enable location support in devicesupport -// LOG.debug("Location CoreService: {}", coreService); -// -// 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); -// -// //TODO: check and follow the preference in coordinator (see R.xml.devicesettings_workout_send_gps_to_band ) -// if(locationUpdatedSetEnabledRequest.getEnabled()) { -// response.addRequests(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder() -// .setRequested(locationUpdatedSetEnabledRequest.getRequests(0).getRequested()) -// .setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK)); -// } -// -// deviceSupport.processLocationUpdateRequest(locationUpdatedSetEnabledRequest.getEnabled(), locationUpdatedSetEnabledRequest.getRequestsList()); -// -// return GdiSmartProto.Smart.newBuilder().setCoreService( -// GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build(); -// } -// LOG.warn("Unknown CoreService request: {}", coreService); -// return null; -// } + 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()) { + 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()) { + LOG.debug("Location CoreService: {}", coreService); + + final GdiCore.CoreService.LocationUpdatedSetEnabledRequest locationUpdatedSetEnabledRequest = coreService.getLocationUpdatedSetEnabledRequest(); + + LOG.info("Received locationUpdatedSetEnabledRequest status: {}", locationUpdatedSetEnabledRequest.getEnabled()); + + GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Builder response = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.newBuilder(); + + final boolean sendGpsPref = deviceSupport.getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false); + if (locationUpdatedSetEnabledRequest.getEnabled() && sendGpsPref) { + response.setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.OK) + .addRequests(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder() + .setRequested(locationUpdatedSetEnabledRequest.getRequests(0).getRequested()) + .setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK)); + } else { + response.setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.UNAVAILABLE); + } + + deviceSupport.processLocationUpdateRequest(locationUpdatedSetEnabledRequest.getEnabled(), locationUpdatedSetEnabledRequest.getRequestsList()); + + return GdiSmartProto.Smart.newBuilder().setCoreService( + GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build(); + } + + LOG.warn("Unknown CoreService request: {}", coreService); + return null; + } private void processProtobufFindMyWatchResponse(GdiFindMyWatch.FindMyWatchService findMyWatchService) { if (findMyWatchService.hasCancelRequest()) { diff --git a/app/src/main/res/xml/devicesettings_root_location.xml b/app/src/main/res/xml/devicesettings_root_location.xml new file mode 100644 index 000000000..2a936edaa --- /dev/null +++ b/app/src/main/res/xml/devicesettings_root_location.xml @@ -0,0 +1,9 @@ + + + + +