diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java
index 3eeb54a96..1ade75964 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java
@@ -36,12 +36,15 @@ import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsGpxRouteInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuami2021FWInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
@@ -50,8 +53,27 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public abstract class Huami2021Coordinator extends HuamiCoordinator {
+ public abstract AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context);
+
@Override
- public abstract InstallHandler findInstallHandler(final Uri uri, final Context context);
+ public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+ if (supportsAgpsUpdates()) {
+ final ZeppOsAgpsInstallHandler agpsInstallHandler = new ZeppOsAgpsInstallHandler(uri, context);
+ if (agpsInstallHandler.isValid()) {
+ return agpsInstallHandler;
+ }
+ }
+
+ if (supportsGpxUploads()) {
+ final ZeppOsGpxRouteInstallHandler gpxRouteInstallHandler = new ZeppOsGpxRouteInstallHandler(uri, context);
+ if (gpxRouteInstallHandler.isValid()) {
+ return gpxRouteInstallHandler;
+ }
+ }
+
+ final AbstractHuami2021FWInstallHandler handler = createFwInstallHandler(uri, context);
+ return handler.isValid() ? handler : null;
+ }
@Override
public boolean supportsHeartRateMeasurement(final GBDevice device) {
@@ -339,6 +361,14 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
return false;
}
+ public boolean supportsAgpsUpdates() {
+ return false;
+ }
+
+ public boolean supportsGpxUploads() {
+ return false;
+ }
+
public boolean supportsControlCenter() {
// TODO: Auto-detect control center?
return false;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java
index a883072ca..000a906a2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitband7/AmazfitBand7Coordinator.java
@@ -57,9 +57,8 @@ public class AmazfitBand7Coordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitBand7FWInstallHandler handler = new AmazfitBand7FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitBand7FWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java
index becc670e6..7fdf02e35 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3/AmazfitGTR3Coordinator.java
@@ -57,9 +57,8 @@ public class AmazfitGTR3Coordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitGTR3FWInstallHandler handler = new AmazfitGTR3FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTR3FWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java
index aab023598..a377b82ef 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr3pro/AmazfitGTR3ProCoordinator.java
@@ -57,9 +57,8 @@ public class AmazfitGTR3ProCoordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitGTR3ProFWInstallHandler handler = new AmazfitGTR3ProFWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTR3ProFWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
index 7f2bcbe11..bcfb14020 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
@@ -25,13 +25,12 @@ import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
-import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuami2021FWInstallHandler;
public class AmazfitGTR4Coordinator extends Huami2021Coordinator {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTR4Coordinator.class);
@@ -58,13 +57,8 @@ public class AmazfitGTR4Coordinator extends Huami2021Coordinator {
}
@Override
- public InstallHandler findInstallHandler(final Uri uri, final Context context) {
- final ZeppOsAgpsInstallHandler agpsInstallHandler = new ZeppOsAgpsInstallHandler(uri, context);
- if (agpsInstallHandler.isValid()) {
- return agpsInstallHandler;
- }
- final AmazfitGTR4FWInstallHandler handler = new AmazfitGTR4FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTR4FWInstallHandler(uri, context);
}
@Override
@@ -72,6 +66,16 @@ public class AmazfitGTR4Coordinator extends Huami2021Coordinator {
return true;
}
+ @Override
+ public boolean supportsAgpsUpdates() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsGpxUploads() {
+ return true;
+ }
+
@Override
public boolean supportsControlCenter() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java
index f3fadf3e1..d2266ef2e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts3/AmazfitGTS3Coordinator.java
@@ -57,9 +57,8 @@ public class AmazfitGTS3Coordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitGTS3FWInstallHandler handler = new AmazfitGTS3FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTS3FWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java
index 333a508a9..8f96ffa7c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4/AmazfitGTS4Coordinator.java
@@ -25,13 +25,12 @@ import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
-import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuami2021FWInstallHandler;
public class AmazfitGTS4Coordinator extends Huami2021Coordinator {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTS4Coordinator.class);
@@ -58,13 +57,8 @@ public class AmazfitGTS4Coordinator extends Huami2021Coordinator {
}
@Override
- public InstallHandler findInstallHandler(final Uri uri, final Context context) {
- final ZeppOsAgpsInstallHandler agpsInstallHandler = new ZeppOsAgpsInstallHandler(uri, context);
- if (agpsInstallHandler.isValid()) {
- return agpsInstallHandler;
- }
- final AmazfitGTS4FWInstallHandler handler = new AmazfitGTS4FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTS4FWInstallHandler(uri, context);
}
@Override
@@ -72,6 +66,16 @@ public class AmazfitGTS4Coordinator extends Huami2021Coordinator {
return true;
}
+ @Override
+ public boolean supportsAgpsUpdates() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsGpxUploads() {
+ return true;
+ }
+
@Override
public boolean supportsControlCenter() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java
index bf11cd31e..4abf0f513 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgts4mini/AmazfitGTS4MiniCoordinator.java
@@ -57,9 +57,8 @@ public class AmazfitGTS4MiniCoordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitGTS4MiniFWInstallHandler handler = new AmazfitGTS4MiniFWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitGTS4MiniFWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java
index a9316117a..920ce80d3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfittrex2/AmazfitTRex2Coordinator.java
@@ -57,9 +57,8 @@ public class AmazfitTRex2Coordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final AmazfitTRex2FWInstallHandler handler = new AmazfitTRex2FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new AmazfitTRex2FWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java
index d7ecb8890..97850b316 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband7/MiBand7Coordinator.java
@@ -57,9 +57,8 @@ public class MiBand7Coordinator extends Huami2021Coordinator {
}
@Override
- public AbstractHuami2021FWInstallHandler findInstallHandler(final Uri uri, final Context context) {
- final MiBand7FWInstallHandler handler = new MiBand7FWInstallHandler(uri, context);
- return handler.isValid() ? handler : null;
+ public AbstractHuami2021FWInstallHandler createFwInstallHandler(final Uri uri, final Context context) {
+ return new MiBand7FWInstallHandler(uri, context);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java
new file mode 100644
index 000000000..bf22362f8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/zeppos/ZeppOsGpxRouteInstallHandler.java
@@ -0,0 +1,119 @@
+/* Copyright (C) 2023 José Rebelo
+
+ 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.devices.huami.zeppos;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteFile;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
+
+public class ZeppOsGpxRouteInstallHandler implements InstallHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteInstallHandler.class);
+
+ protected final Context mContext;
+ private ZeppOsGpxRouteFile file;
+
+ public ZeppOsGpxRouteInstallHandler(final Uri uri, final Context context) {
+ this.mContext = context;
+
+ final UriHelper uriHelper;
+ try {
+ uriHelper = UriHelper.get(uri, context);
+ } catch (final IOException e) {
+ LOG.error("Failed to get uri", e);
+ return;
+ }
+ try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
+ final byte[] rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB
+ final ZeppOsGpxRouteFile gpxFile = new ZeppOsGpxRouteFile(rawBytes);
+ if (gpxFile.isValid()) {
+ this.file = gpxFile;
+ }
+ } catch (final Exception e) {
+ LOG.error("Failed to read file", e);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return file != null;
+ }
+
+ @Override
+ public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+ if (device.isBusy()) {
+ installActivity.setInfoText(device.getBusyTask());
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ if (!device.isInitialized()) {
+ installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ final GenericItem fwItem = createInstallItem(device);
+ fwItem.setIcon(device.getType().getIcon());
+
+ if (file == null) {
+ fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+ installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ final String gpxRoute = mContext.getString(R.string.kind_gpx_route);
+ builder.append(mContext.getString(R.string.fw_upgrade_notice, gpxRoute));
+ installActivity.setInfoText(builder.toString());
+ installActivity.setInstallItem(fwItem);
+ installActivity.setInstallEnabled(true);
+ }
+
+ @Override
+ public void onStartInstall(final GBDevice device) {
+ }
+
+ public ZeppOsGpxRouteFile getFile() {
+ return file;
+ }
+
+ private GenericItem createInstallItem(final GBDevice device) {
+ final String firmwareName = mContext.getString(
+ R.string.installhandler_firmware_name,
+ mContext.getString(device.getType().getName()),
+ mContext.getString(R.string.kind_gpx_route),
+ file.getName()
+ );
+ return new GenericItem(firmwareName);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java
index c434fb70d..e3349adb6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java
@@ -81,6 +81,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsGpxRouteInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
@@ -106,6 +107,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.Upd
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2021;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteUploadOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlarmsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCalendarService;
@@ -656,11 +658,28 @@ public abstract class Huami2021Support extends HuamiSupport {
configService
).perform();
} catch (final Exception e) {
- GB.toast(getContext(), "AGPS File cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+ GB.toast(getContext(), "AGPS file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
- } else {
- super.onInstallApp(uri);
+
+ return;
}
+
+ final ZeppOsGpxRouteInstallHandler gpxRouteHandler = new ZeppOsGpxRouteInstallHandler(uri, getContext());
+ if (gpxRouteHandler.isValid()) {
+ try {
+ new ZeppOsGpxRouteUploadOperation(
+ this,
+ gpxRouteHandler.getFile(),
+ fileUploadService
+ ).perform();
+ } catch (final Exception e) {
+ GB.toast(getContext(), "Gpx route file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+ }
+
+ return;
+ }
+
+ super.onInstallApp(uri);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java
new file mode 100644
index 000000000..5eeafe081
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteFile.java
@@ -0,0 +1,208 @@
+/* Copyright (C) 2023 José Rebelo
+
+ 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.huami.zeppos.operations;
+
+import android.location.Location;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParseException;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
+import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxWaypoint;
+
+public class ZeppOsGpxRouteFile {
+ private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteFile.class);
+
+ private static final double COORD_MULTIPLIER = 3000000.0;
+
+ public static final byte[] XML_HEADER = new byte[]{
+ '<', '?', 'x', 'm', 'l'
+ };
+
+ // Some gpx files start with " trackPoints = gpxFile.getPoints();
+ final List waypoints = gpxFile.getWaypoints();
+
+ double minLatitude = 180;
+ double maxLatitude = -180;
+ double minLongitude = 180;
+ double maxLongitude = -180;
+ double minAltitude = 10000;
+ double maxAltitude = -10000;
+
+ for (final GPSCoordinate coord : trackPoints) {
+ minLatitude = Math.min(minLatitude, coord.getLatitude());
+ maxLatitude = Math.max(maxLatitude, coord.getLatitude());
+ minLongitude = Math.min(minLongitude, coord.getLongitude());
+ maxLongitude = Math.max(maxLongitude, coord.getLongitude());
+ minAltitude = Math.min(minAltitude, coord.getAltitude());
+ maxAltitude = Math.max(maxAltitude, coord.getAltitude());
+ }
+
+ try {
+ baos.write(BLETypeConversions.fromUint32(0)); // ?
+ baos.write(BLETypeConversions.fromUint32(0x54)); // ?
+ baos.write(BLETypeConversions.fromUint32(0x01)); // ?
+ baos.write(BLETypeConversions.fromUint32((int) timestamp));
+ baos.write(BLETypeConversions.fromUint32((int) (minLatitude * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) (maxLatitude * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) (minLongitude * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) (maxLongitude * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) minAltitude));
+ baos.write(BLETypeConversions.fromUint32((int) maxAltitude));
+ baos.write(truncatePadString(getName()));
+ baos.write(BLETypeConversions.fromUint32(0)); // ?
+
+ if (!waypoints.isEmpty()) {
+ baos.write(BLETypeConversions.fromUint32(2));
+ baos.write(BLETypeConversions.fromUint32(waypoints.size() * 68));
+ for (final GpxWaypoint waypoint : waypoints) {
+ baos.write(BLETypeConversions.fromUint32(0x1a)); // ?
+ baos.write(BLETypeConversions.fromUint32((int) (waypoint.getLatitude() * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) (waypoint.getLongitude() * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) waypoint.getAltitude()));
+ baos.write(truncatePadString(waypoint.getName()));
+ baos.write(BLETypeConversions.fromUint32(0)); // ?
+ }
+ }
+
+ baos.write(BLETypeConversions.fromUint32(1)); // ?
+ baos.write(BLETypeConversions.fromUint32(trackPoints.size() * 14));
+
+ // Keep track of the total distance
+ double totalDist = 0;
+ GPSCoordinate prevPoint = trackPoints.isEmpty() ? null : trackPoints.get(0);
+
+ for (final GPSCoordinate point : trackPoints) {
+ totalDist += distanceBetween(prevPoint, point);
+
+ baos.write(BLETypeConversions.fromUint32((int) totalDist));
+ baos.write(BLETypeConversions.fromUint32((int) (point.getLatitude() * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint32((int) (point.getLongitude() * COORD_MULTIPLIER)));
+ baos.write(BLETypeConversions.fromUint16((int) point.getAltitude()));
+
+ prevPoint = point;
+ }
+ } catch (final IOException e) {
+ LOG.error("Failed to encode gpx file", e);
+ return null;
+ }
+
+ return baos.toByteArray();
+ }
+
+ public static double distanceBetween(final GPSCoordinate a, final GPSCoordinate b) {
+ final Location start = new Location("start");
+ start.setLatitude(a.getLatitude());
+ start.setLongitude(a.getLongitude());
+
+ final Location end = new Location("end");
+ end.setLatitude(b.getLatitude());
+ end.setLongitude(b.getLongitude());
+
+ return end.distanceTo(start);
+ }
+
+ /**
+ * Truncates / pads a string to 48 bytes (including null terminator).
+ */
+ public static byte[] truncatePadString(final String s) {
+ final ByteBuffer buf = ByteBuffer.allocate(48);
+ buf.put(StringUtils.truncateToBytes(s, 47));
+ return buf.array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java
new file mode 100644
index 000000000..a4dc7aa1b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/operations/ZeppOsGpxRouteUploadOperation.java
@@ -0,0 +1,100 @@
+/* Copyright (C) 2023 José Rebelo
+
+ 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.huami.zeppos.operations;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation
+ implements ZeppOsFileUploadService.Callback {
+ private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteUploadOperation.class);
+
+ private final ZeppOsGpxRouteFile file;
+ private final byte[] fileBytes;
+
+ private final ZeppOsFileUploadService fileUploadService;
+
+ public ZeppOsGpxRouteUploadOperation(final Huami2021Support support,
+ final ZeppOsGpxRouteFile file,
+ final ZeppOsFileUploadService fileUploadService) {
+ super(support);
+ this.file = file;
+ this.fileBytes = file.getEncodedBytes();
+ this.fileUploadService = fileUploadService;
+ }
+
+ @Override
+ protected void doPerform() throws IOException {
+ fileUploadService.sendFile(
+ "sport://file_transfer?appId=7073283073¶ms={}",
+ "track_" + file.getTimestamp() + ".dat",
+ fileBytes,
+ this
+ );
+ }
+
+ @Override
+ protected void operationFinished() {
+ operationStatus = OperationStatus.FINISHED;
+ if (getDevice() != null && getDevice().isConnected()) {
+ unsetBusy();
+ getDevice().sendDeviceUpdateIntent(getContext());
+ }
+ }
+
+ @Override
+ public void onFileUploadFinish(final boolean success) {
+ LOG.info("Finished gpx route upload operation, success={}", success);
+
+ final String notificationMessage = success ?
+ getContext().getString(R.string.gpx_route_upload_complete) :
+ getContext().getString(R.string.gpx_route_upload_failed);
+
+ GB.updateInstallNotification(notificationMessage, false, 100, getContext());
+
+ operationFinished();
+ }
+
+ @Override
+ public void onFileUploadProgress(final int progress) {
+ LOG.trace("Gpx route upload operation progress: {}", progress);
+
+ final int progressPercent = (int) ((((float) (progress)) / fileBytes.length) * 100);
+ updateProgress(progressPercent);
+ }
+
+ private void updateProgress(final int progressPercent) {
+ try {
+ final TransactionBuilder builder = performInitialized("send gpx route upload progress");
+ builder.add(new SetProgressAction(getContext().getString(R.string.gpx_route_upload_in_progress), true, progressPercent, getContext()));
+ builder.queue(getQueue());
+ } catch (final Exception e) {
+ LOG.error("Failed to update progress notification", e);
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
index 854f93013..76918c30b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
@@ -46,6 +46,27 @@ public class StringUtils {
return s.substring(0, length);
}
+ /**
+ * Truncate a string to a certain maximum number of bytes, assuming UTF-8 encoding.
+ * Does not include the null terminator. Due to multi-byte characters, it's possible
+ * that the resulting array is smaller than len, but never larger.
+ */
+ public static byte[] truncateToBytes(final String s, final int len) {
+ if (StringUtils.isNullOrEmpty(s)) {
+ return new byte[]{};
+ }
+
+ int i = 0;
+ while (++i < s.length()) {
+ final String subString = s.substring(0, i + 1);
+ if (subString.getBytes(StandardCharsets.UTF_8).length > len) {
+ break;
+ }
+ }
+
+ return s.substring(0, i).getBytes(StandardCharsets.UTF_8);
+ }
+
public static int utf8ByteLength(String string, int length) {
if (string == null) {
return 0;
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1a99dac88..2fff0d7db 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -791,6 +791,9 @@
Firmware installation complete
Firmware installation complete, rebooting device…
Firmware flashing failed
+ Gpx route upload failed
+ Gpx route upload complete
+ Uploading gpx route
Steps
Calories
Distance
@@ -1195,6 +1198,7 @@
GPS Almanac
GPS Error Correction
AGPS Bundle
+ GPX Route
Resources
Watchface
App