From a95820d09e266f37fb833cc149389eaea8fa952a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 8 Aug 2023 22:11:14 +0100 Subject: [PATCH] Bangle.js: Fetch activity data --- .../gadgetbridge/daogen/GBDaoGenerator.java | 3 +- .../schema/GadgetbridgeUpdate_51.java | 38 +++++ .../devices/banglejs/BangleJSConstants.java | 4 + .../devices/banglejs/BangleJSCoordinator.java | 11 +- .../banglejs/BangleJSSampleProvider.java | 54 ++++++- .../banglejs/BangleJSSettingsCustomizer.java | 141 ++++++++++++++++++ .../GalaxyBudsProDeviceCoordinator.java | 1 - .../banglejs/BangleJSDeviceSupport.java | 136 ++++++++++++----- app/src/main/res/values/strings.xml | 3 + .../xml/devicesettings_banglejs_activity.xml | 23 +++ 10 files changed, 370 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_51.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSSettingsCustomizer.java create mode 100644 app/src/main/res/xml/devicesettings_banglejs_activity.xml 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 @@ + + + + + + + + + +