From 880e18fffcb80bc8b9308d3b1455cb145e54bf70 Mon Sep 17 00:00:00 2001 From: kuhy Date: Fri, 3 May 2024 16:36:16 +0200 Subject: [PATCH] Garmin protocol: show AGPS data status in settings --- .../DeviceSettingsPreferenceConst.java | 1 + .../garmin/GarminAgpsInstallHandler.java | 2 +- .../devices/garmin/GarminCoordinator.java | 9 +++ .../garmin/GarminSettingsCustomizer.java | 72 +++++++++++++++++++ .../service/devices/garmin/GarminSupport.java | 6 ++ .../AgpsHandler.java} | 31 ++++++-- .../{file => agps}/GarminAgpsDataType.java | 2 +- .../garmin/{file => agps}/GarminAgpsFile.java | 2 +- .../devices/garmin/agps/GarminAgpsStatus.java | 23 ++++++ .../devices/garmin/http/HttpHandler.java | 15 ++-- app/src/main/res/values/strings.xml | 5 ++ .../res/xml/devicesettings_garmin_agps.xml | 18 +++++ 12 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/{http/EphemerisHandler.java => agps/AgpsHandler.java} (63%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/{file => agps}/GarminAgpsDataType.java (98%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/{file => agps}/GarminAgpsFile.java (99%) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java create mode 100644 app/src/main/res/xml/devicesettings_garmin_agps.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 8652340a9c..480721637a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -231,6 +231,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_AGPS_EXPIRY_REMINDER_TIME = "pref_agps_expiry_reminder_time"; public static final String PREF_AGPS_UPDATE_TIME = "pref_agps_update_time"; public static final String PREF_AGPS_EXPIRE_TIME = "pref_agps_expire_time"; + public static final String PREF_AGPS_STATUS = "pref_agps_status"; public static final String PREF_FIND_PHONE = "prefs_find_phone"; public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminAgpsInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminAgpsInstallHandler.java index 26304553fa..add4595844 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminAgpsInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminAgpsInstallHandler.java @@ -16,7 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file.GarminAgpsFile; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsFile; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; 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 df00167346..68bffb2be3 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 @@ -11,6 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; @@ -54,6 +55,9 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { final List location = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.LOCATION); location.add(R.xml.devicesettings_workout_send_gps_to_band); + if (supportsAgpsUpdates()) { + location.add(R.xml.devicesettings_garmin_agps); + } final List connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION); connection.add(R.xml.devicesettings_high_mtu); @@ -64,6 +68,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return deviceSpecificSettings; } + @Override + public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) { + return new GarminSettingsCustomizer(); + } + @Override public boolean supportsActivityDataFetching() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java new file mode 100644 index 0000000000..60fb015834 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminSettingsCustomizer.java @@ -0,0 +1,72 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.garmin; + +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomizer { + + @Override + public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) { + } + + @Override + public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs) { + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + final Preference prefAgpsUpdateTime = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME); + if (prefAgpsUpdateTime != null) { + final long ts = prefs.getLong(DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME, 0L); + if (ts > 0) { + prefAgpsUpdateTime.setSummary(sdf.format(new Date(ts))); + } else { + prefAgpsUpdateTime.setSummary(handler.getContext().getString(R.string.unknown)); + } + } + + final Preference prefAgpsStatus = handler.findPreference(DeviceSettingsPreferenceConst.PREF_AGPS_STATUS); + if (prefAgpsStatus != null) { + final GarminAgpsStatus agpsStatus = GarminAgpsStatus.valueOf(prefs.getString(DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.MISSING.name())); + prefAgpsStatus.setSummary(handler.getContext().getString(agpsStatus.getText())); + } + } + + @Override + public Set getPreferenceKeysWithSummary() { + return Collections.emptySet(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public GarminSettingsCustomizer createFromParcel(final Parcel in) { + return new GarminSettingsCustomizer(); + } + + @Override + public GarminSettingsCustomizer[] newArray(final int size) { + return new GarminSettingsCustomizer[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + } +} 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 8944984a9c..c13361230c 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 @@ -27,7 +27,9 @@ import java.util.TimerTask; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminAgpsInstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences; import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability; @@ -48,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmartProto; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v1.CommunicatorV1; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2.CommunicatorV2; @@ -629,6 +632,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni final File agpsFile = getAgpsFile(); try (FileOutputStream outputStream = new FileOutputStream(agpsFile)) { outputStream.write(agpsHandler.getFile().getBytes()); + evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.PENDING.name() + )); LOG.info("AGPS file successfully written to the cache directory."); } catch (final IOException e) { LOG.error("Failed to write AGPS bytes to temporary directory", e); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/EphemerisHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java similarity index 63% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/EphemerisHandler.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java index 86a0eef9d3..02fb58539e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/EphemerisHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/AgpsHandler.java @@ -1,7 +1,8 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http; +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.threeten.bp.Instant; import java.io.File; import java.io.FileInputStream; @@ -11,21 +12,22 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file.GarminAgpsDataType; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GBTarFile; -public class EphemerisHandler { - private static final Logger LOG = LoggerFactory.getLogger(EphemerisHandler.class); +public class AgpsHandler { + private static final Logger LOG = LoggerFactory.getLogger(AgpsHandler.class); private static final String QUERY_CONSTELLATIONS = "constellations"; private final GarminSupport deviceSupport; - public EphemerisHandler(GarminSupport deviceSupport) { + public AgpsHandler(GarminSupport deviceSupport) { this.deviceSupport = deviceSupport; } - public byte[] handleEphemerisRequest(final String path, final Map query) { + public byte[] handleAgpsRequest(final String path, final Map query) { try { if (!query.containsKey(QUERY_CONSTELLATIONS)) { LOG.debug("Query does not contain information about constellations; skipping request."); @@ -45,10 +47,16 @@ public class EphemerisHandler { final GarminAgpsDataType garminAgpsDataType = GarminAgpsDataType.valueOf(constellation); if (!tarFile.containsFile(garminAgpsDataType.getFileName())) { LOG.error("AGPS archive is missing requested file: {}", garminAgpsDataType.getFileName()); + deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.ERROR.name() + )); return null; } } catch (IllegalArgumentException e) { LOG.error("Device requested unsupported AGPS data type: {}", constellation); + deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.ERROR.name() + )); return null; } } @@ -56,7 +64,10 @@ public class EphemerisHandler { return rawBytes; } } catch (IOException e) { - LOG.error("Unable to obtain ephemeris data.", e); + LOG.error("Unable to obtain AGPS data.", e); + deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.ERROR.name() + )); return null; } } @@ -64,6 +75,12 @@ public class EphemerisHandler { public Callable getOnDataSuccessfullySentListener() { return () -> { LOG.info("AGPS data successfully sent to the device."); + deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_UPDATE_TIME, Instant.now().toEpochMilli() + )); + deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences( + DeviceSettingsPreferenceConst.PREF_AGPS_STATUS, GarminAgpsStatus.CURRENT.name() + )); if (deviceSupport.getAgpsFile().delete()) { LOG.info("AGPS data was deleted from the cache folder."); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsDataType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java similarity index 98% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsDataType.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java index 7e8c7e23a9..1ca3e33793 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsDataType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsDataType.java @@ -1,4 +1,4 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file; +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps; public enum GarminAgpsDataType { GLONASS("CPE_GLO.BIN"), QZSS("CPE_QZSS.BIN"), GPS("CPE_GPS.BIN"), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java similarity index 99% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsFile.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java index 2865231270..67923895fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/file/GarminAgpsFile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsFile.java @@ -1,4 +1,4 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file; +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java new file mode 100644 index 0000000000..370c31539a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/agps/GarminAgpsStatus.java @@ -0,0 +1,23 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps; + +import androidx.annotation.StringRes; + +import nodomain.freeyourgadget.gadgetbridge.R; + + +public enum GarminAgpsStatus { + MISSING(R.string.agps_status_missing), // AGPS data file was not yet installed + PENDING(R.string.agps_status_pending), // AGPS data file is waiting for installation + CURRENT(R.string.agps_status_current), // AGPS data was successfully installed + ERROR(R.string.agps_status_error); // Unable to install AGPS data file + + private final @StringRes int text; + + GarminAgpsStatus(@StringRes int text) { + this.text = text; + } + + public @StringRes int getText() { + return text; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java index 9d5c0dd972..5022741738 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/HttpHandler.java @@ -21,6 +21,7 @@ import java.util.zip.GZIPOutputStream; import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiHttpService; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.AgpsHandler; import nodomain.freeyourgadget.gadgetbridge.util.HttpUtils; public class HttpHandler { @@ -30,10 +31,10 @@ public class HttpHandler { //.serializeNulls() .create(); - private final EphemerisHandler ephemerisHandler; + private final AgpsHandler agpsHandler; public HttpHandler(GarminSupport deviceSupport) { - ephemerisHandler = new EphemerisHandler(deviceSupport); + agpsHandler = new AgpsHandler(deviceSupport); } public GdiHttpService.HttpService handle(final GdiHttpService.HttpService httpService) { @@ -78,13 +79,13 @@ public class HttpHandler { LOG.debug("Weather response: {}", json); return createRawResponse(rawRequest, json.getBytes(StandardCharsets.UTF_8), "application/json", null); } else if (path.startsWith("/ephemeris/")) { - LOG.info("Got ephemeris request for {}", path); - final byte[] ephemerisData = ephemerisHandler.handleEphemerisRequest(path, query); - if (ephemerisData == null) { + LOG.info("Got AGPS request for {}", path); + final byte[] agpsData = agpsHandler.handleAgpsRequest(path, query); + if (agpsData == null) { return null; } - LOG.debug("Successfully obtained ephemeris data (length: {})", ephemerisData.length); - return createRawResponse(rawRequest, ephemerisData, "application/x-tar", ephemerisHandler.getOnDataSuccessfullySentListener()); + LOG.debug("Successfully obtained AGPS data (length: {})", agpsData.length); + return createRawResponse(rawRequest, agpsData, "application/x-tar", agpsHandler.getOnDataSuccessfullySentListener()); } else { LOG.warn("Unhandled path {}", urlString); return null; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6d6f471bc..a4fe16d098 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -535,6 +535,11 @@ AGPS Expiry Reminder Time AGPS Update Time AGPS Expire Time + AGPS Status + Missing + Pending + Current + Error Camera Remote Allows the watch to trigger the phone\'s camera Morning Updates diff --git a/app/src/main/res/xml/devicesettings_garmin_agps.xml b/app/src/main/res/xml/devicesettings_garmin_agps.xml new file mode 100644 index 0000000000..562ad28400 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_garmin_agps.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file