diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index 7992ec81b..d5c7aa51b 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
- final Schema schema = new Schema(50, MAIN_PACKAGE + ".entities");
+ final Schema schema = new Schema(51, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -613,6 +613,7 @@ public class GBDaoGenerator {
Entity activitySample = addEntity(schema, "BangleJSActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+ activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java
new file mode 100644
index 000000000..66e34123c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java
@@ -0,0 +1,38 @@
+/* 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.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
+
+public class GadgetbridgeUpdate_51 implements DBUpdateScript {
+ @Override
+ public void upgradeSchema(final SQLiteDatabase db) {
+ if (!DBHelper.existsColumn(BangleJSActivitySampleDao.TABLENAME, BangleJSActivitySampleDao.Properties.RawIntensity.columnName, db)) {
+ final String sqlAddColumnRawIntensity = "ALTER TABLE " + BangleJSActivitySampleDao.TABLENAME + " ADD COLUMN "
+ + BangleJSActivitySampleDao.Properties.RawIntensity.columnName + " INTEGER DEFAULT -1 NOT NULL";
+ db.execSQL(sqlAddColumnRawIntensity);
+ }
+ }
+
+ @Override
+ public void downgradeSchema(final SQLiteDatabase db) {
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java
index 8bcbb38df..b56df1bad 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSConstants.java
@@ -25,4 +25,8 @@ public final class BangleJSConstants {
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+ public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER = "pref_banglejs_activity_full_sync_trigger";
+ public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS = "pref_banglejs_activity_full_sync_status";
+ public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START = "pref_banglejs_activity_full_sync_start";
+ public static final String PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP = "pref_banglejs_activity_full_sync_stop";
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
index 159c12611..8190d184c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
@@ -34,6 +34,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
@@ -111,7 +112,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public boolean supportsActivityDataFetching() {
- return false;
+ return true;
}
@Override
@@ -188,6 +189,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
return true;
}
+ @Override
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
final List settings = new ArrayList<>();
@@ -205,6 +207,8 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
if (BuildConfig.INTERNET_ACCESS)
settings.add(R.xml.devicesettings_device_internet_access);
+ settings.add(R.xml.devicesettings_banglejs_activity);
+
settings.add(R.xml.devicesettings_header_developer);
settings.add(R.xml.devicesettings_banglejs_apploader);
settings.add(R.xml.devicesettings_device_intents);
@@ -212,6 +216,11 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
}
+ @Override
+ public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
+ return new BangleJSSettingsCustomizer(device);
+ }
+
@Override
public boolean supportsNavigation() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java
index 74e736118..71322f9ee 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSampleProvider.java
@@ -19,18 +19,23 @@ package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
-import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySample;
-import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class BangleJSSampleProvider extends AbstractSampleProvider {
+ private static final Logger LOG = LoggerFactory.getLogger(BangleJSSampleProvider.class);
+
public BangleJSSampleProvider(GBDevice device, DaoSession session) {
super(device, session);
}
@@ -77,11 +82,54 @@ public class BangleJSSampleProvider extends AbstractSampleProvider nearSamples = getGBActivitySamples(
+ sample.getTimestamp() - 60 * 2,
+ sample.getTimestamp() + 60 * 2,
+ normalizeType(sample.getRawKind())
+ );
+
+ if (nearSamples.isEmpty()) {
+ // No nearest sample, just insert
+ LOG.debug("No duplicate found at {}, inserting", sample.getTimestamp());
+ addGBActivitySample(sample);
+ return;
+ }
+
+ BangleJSActivitySample nearestSample = nearSamples.get(0);
+
+ for (final BangleJSActivitySample s : nearSamples) {
+ final int curDist = Math.abs(nearestSample.getTimestamp() - s.getTimestamp());
+ final int newDist = Math.abs(sample.getTimestamp() - s.getTimestamp());
+ if (newDist < curDist) {
+ nearestSample = s;
+ }
+ }
+
+ LOG.debug("Found {} duplicates for {}, updating nearest sample at {}", nearSamples.size(), sample.getTimestamp(), nearestSample.getTimestamp());
+
+ if (sample.getHeartRate() != 0) {
+ nearestSample.setHeartRate(sample.getHeartRate());
+ }
+ if (sample.getSteps() != 0) {
+ nearestSample.setSteps(sample.getSteps());
+ }
+ if (sample.getRawIntensity() != 0) {
+ nearestSample.setRawIntensity(sample.getRawIntensity());
+ }
+
+ addGBActivitySample(nearestSample);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java
new file mode 100644
index 000000000..64f9bee10
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java
@@ -0,0 +1,141 @@
+/* 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.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+
+import androidx.preference.EditTextPreference;
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class BangleJSSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+ private ProgressDialog activityFullSyncDialog;
+
+ final GBDevice device;
+
+ public BangleJSSettingsCustomizer(final GBDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
+ // Handle full sync status
+ if (preference.getKey().equals(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS)) {
+ final EditTextPreference fullSyncStatusPreference = (EditTextPreference) preference;
+ final String statusValue = fullSyncStatusPreference.getText();
+
+ if (activityFullSyncDialog != null) {
+ switch (statusValue) {
+ case "start":
+ activityFullSyncDialog.setMessage(handler.getContext().getString(R.string.busy_task_fetch_activity_data));
+ break;
+ case "end":
+ activityFullSyncDialog.dismiss();
+ activityFullSyncDialog = null;
+ break;
+ default:
+ activityFullSyncDialog.setMessage(statusValue);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
+ final Preference fullSyncPref = handler.findPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_TRIGGER);
+ if (fullSyncPref != null) {
+ fullSyncPref.setOnPreferenceClickListener(preference -> {
+ if (activityFullSyncDialog != null) {
+ // Already syncing
+ return true;
+ }
+
+ final Context context = preference.getContext();
+
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.pref_activity_full_sync_trigger_title)
+ .setMessage(R.string.pref_activity_full_sync_trigger_warning)
+ .setIcon(R.drawable.ic_refresh)
+ .setPositiveButton(R.string.start, (dialog, whichButton) -> {
+ handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START);
+
+ activityFullSyncDialog = new ProgressDialog(context);
+ activityFullSyncDialog.setCancelable(false);
+ activityFullSyncDialog.setMessage(context.getString(R.string.sony_anc_optimizer_status_starting));
+ activityFullSyncDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ activityFullSyncDialog.setProgress(0);
+ activityFullSyncDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.Cancel), (dialog1, which) -> {
+ dialog1.dismiss();
+ activityFullSyncDialog = null;
+ handler.notifyPreferenceChanged(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STOP);
+ });
+
+ activityFullSyncDialog.show();
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+
+ return true;
+ });
+ }
+ }
+
+ @Override
+ public Set getPreferenceKeysWithSummary() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeParcelable(device, 0);
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public BangleJSSettingsCustomizer createFromParcel(final Parcel in) {
+ final GBDevice device = in.readParcelable(BangleJSSettingsCustomizer.class.getClassLoader());
+ return new BangleJSSettingsCustomizer(device);
+ }
+
+ @Override
+ public BangleJSSettingsCustomizer[] newArray(final int size) {
+ return new BangleJSSettingsCustomizer[size];
+ }
+ };
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java
index f3966a285..c9ade8997 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/galaxy_buds/GalaxyBudsProDeviceCoordinator.java
@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
-import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
index e58d8ab8a..e431c0ec0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
@@ -25,6 +25,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTENTS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.getUser;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START;
+import static nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants.PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -32,6 +34,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -72,6 +75,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -89,6 +93,7 @@ import io.wax911.emojify.EmojiManager;
import io.wax911.emojify.EmojiUtils;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
@@ -96,6 +101,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallContro
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
@@ -105,6 +111,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
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.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@@ -117,6 +124,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
@@ -328,27 +336,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
getDevice().setFirmwareVersion2("N/A");
lastBatteryPercent = -1;
- /* Here we get the last Activity info saved from Bangle.js, and then send
- its timestamp. Bangle.js can then look back at its history and can try and
- send any missing data. */
- try (DBHandler dbHandler = GBApplication.acquireDB()) {
- BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
- BangleJSActivitySample sample = provider.getLatestActivitySample();
- if (sample!=null) {
- LOG.info("Send 'actlast' with last activity's timestamp: "+sample.getTimestamp());
- try {
- JSONObject o = new JSONObject();
- o.put("t", "actlast");
- o.put("time", sample.getTimestamp());
- uartTxJSON("actlast", o);
- } catch (JSONException e) {
- LOG.info("JSONException: " + e.getLocalizedMessage());
- }
- }
- } catch (Exception ex) {
- LOG.warn("Error getting last activity: " + ex.getLocalizedMessage());
- }
-
LOG.info("Initialization Done");
requestBangleGPSPowerStatus();
@@ -547,6 +534,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
case "notify" :
handleNotificationControl(json);
break;
+ case "actfetch":
+ handleActivityFetch(json);
+ break;
case "act":
handleActivity(json);
break;
@@ -626,17 +616,36 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
evaluateGBDeviceEvent(deviceEvtNotificationControl);
}
+ private void handleActivityFetch(final JSONObject json) throws JSONException {
+ final String state = json.getString("state");
+ if ("start".equals(state)) {
+ GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data),"", true, 0, getContext());
+ getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_activity_data));
+ } else if ("end".equals(state)) {
+ saveLastSyncTimestamp(System.currentTimeMillis() - 1000L * 60);
+ getDevice().unsetBusyTask();
+ GB.updateTransferNotification(null, "", false, 100, getContext());
+ } else {
+ LOG.warn("Unknown actfetch state {}", state);
+ }
+
+ final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
+ .withPreference(PREF_BANGLEJS_ACTIVITY_FULL_SYNC_STATUS, state);
+ evaluateGBDeviceEvent(event);
+
+ getDevice().sendDeviceUpdateIntent(getContext());
+ }
+
/**
* Handle "act" packet, used to send activity reports
*/
private void handleActivity(JSONObject json) throws JSONException {
BangleJSActivitySample sample = new BangleJSActivitySample();
- sample.setTimestamp((int) (System.currentTimeMillis() / 1000L));
- int hrm = 0;
- int steps = 0;
- if (json.has("time")) sample.setTimestamp(json.getInt("time"));
- if (json.has("hrm")) hrm = json.getInt("hrm");
- if (json.has("stp")) steps = json.getInt("stp");
+ int timestamp = (int) (json.optLong("ts", System.currentTimeMillis()) / 1000);
+ int hrm = json.optInt("hrm", 0);
+ int steps = json.optInt("stp", 0);
+ int intensity = json.optInt("mov", ActivitySample.NOT_MEASURED);
+ boolean realtime = json.optInt("rt", 0) == 1;
int activity = BangleJSSampleProvider.TYPE_ACTIVITY;
/*if (json.has("act")) {
String actName = "TYPE_" + json.getString("act").toUpperCase();
@@ -651,21 +660,26 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
LOG.info("JSON activity '"+actName+"' not found");
}
}*/
+ sample.setTimestamp(timestamp);
sample.setRawKind(activity);
sample.setHeartRate(hrm);
sample.setSteps(steps);
- try (DBHandler dbHandler = GBApplication.acquireDB()) {
- Long userId = getUser(dbHandler.getDaoSession()).getId();
- Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
- BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
- sample.setDeviceId(deviceId);
- sample.setUserId(userId);
- provider.addGBActivitySample(sample);
- } catch (Exception ex) {
- LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
+ sample.setRawIntensity(intensity);
+ if (!realtime) {
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ final Long userId = getUser(dbHandler.getDaoSession()).getId();
+ final Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
+ BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
+ sample.setDeviceId(deviceId);
+ sample.setUserId(userId);
+ provider.upsertSample(sample);
+ } catch (final Exception ex) {
+ LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
+ }
}
+
// push realtime data
- if (realtimeHRM || realtimeStep) {
+ if (realtime && (realtimeHRM || realtimeStep)) {
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
@@ -942,6 +956,17 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
return intent;
}
+ @Override
+ public void onSendConfiguration(final String config) {
+ switch (config) {
+ case PREF_BANGLEJS_ACTIVITY_FULL_SYNC_START:
+ fetchActivityData(0);
+ return;
+ }
+
+ LOG.warn("Unknown config changed: {}", config);
+ }
+
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@@ -1306,7 +1331,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onFetchRecordedData(int dataTypes) {
- if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) {
+ if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
+ fetchActivityData(getLastSuccessfulSyncTime());
+ }
+
+ if ((dataTypes & RecordedDataTypes.TYPE_DEBUGLOGS) != 0) {
File dir;
try {
dir = FileUtils.getExternalFilesDir();
@@ -1329,6 +1358,37 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
}
}
+ protected void fetchActivityData(final long timestampMillis) {
+ try {
+ JSONObject o = new JSONObject();
+ o.put("t", "actfetch");
+ o.put("ts", timestampMillis);
+ uartTxJSON("fetch activity data", o);
+ } catch (final JSONException e) {
+ LOG.warn("Failed to fetch activity data", e);
+ }
+ }
+
+ protected String getLastSyncTimeKey() {
+ return "lastSyncTimeMillis";
+ }
+
+ protected void saveLastSyncTimestamp(final long timestamp) {
+ final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
+ editor.putLong(getLastSyncTimeKey(), timestamp);
+ editor.apply();
+ }
+
+ protected long getLastSuccessfulSyncTime() {
+ long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
+ if (timeStampMillis != 0) {
+ return timeStampMillis;
+ }
+ final GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.add(Calendar.DAY_OF_MONTH, -1);
+ return calendar.getTimeInMillis();
+ }
+
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
if (enable == realtimeHRM) return;
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 487cc8e58..99ba9d1b2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2222,4 +2222,7 @@
Next
drag handle
FOUND IT
+ Trigger a full sync of all activity data
+ Full sync
+ This will trigger a full sync of all activity data from the device. It may take a few minutes to complete.
diff --git a/app/src/main/res/xml/devicesettings_banglejs_activity.xml b/app/src/main/res/xml/devicesettings_banglejs_activity.xml
new file mode 100644
index 000000000..e9b277b66
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_banglejs_activity.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+