From c700d49bf09033942989a1fd0a1edb7823fe88df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 21 Sep 2024 19:14:04 +0100 Subject: [PATCH] Allow gadgets to provide distance and calories --- .../freeyourgadget/gadgetbridge/Widget.java | 17 ++-- .../activities/DevicesFragment.java | 9 +- .../activities/charts/ActivityAnalysis.java | 5 ++ .../activities/charts/StepAnalysis.java | 39 +++++++-- .../charts/StepStreaksDashboard.java | 7 +- .../activities/charts/StepsFragment.java | 13 ++- .../adapter/GBDeviceAdapterv2.java | 16 ++-- .../devices/AbstractSampleProvider.java | 68 +++++++++------ .../test/samples/TestSampleProvider.java | 18 ++-- .../entities/AbstractActivitySample.java | 25 +++++- .../gadgetbridge/model/ActivityAmount.java | 9 ++ .../gadgetbridge/model/ActivitySample.java | 10 +++ .../gadgetbridge/model/DailyTotals.java | 84 +++++++++---------- .../gadgetbridge/util/DashboardUtils.java | 26 +++--- 14 files changed, 224 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java index 90f85900b..89b72b6c2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java @@ -61,16 +61,14 @@ public class Widget extends AppWidgetProvider { static BroadcastReceiver broadcastReceiver = null; - private long[] getSteps(GBDevice gbDevice) { + private DailyTotals getSteps(GBDevice gbDevice) { Context context = GBApplication.getContext(); Calendar day = GregorianCalendar.getInstance(); if (!(context instanceof GBApplication)) { - return new long[]{0, 0, 0}; + return new DailyTotals(); } - DailyTotals ds = new DailyTotals(); - return ds.getDailyTotalsForDevice(gbDevice, day); - //return ds.getDailyTotalsForAllDevices(day); + return DailyTotals.getDailyTotalsForDevice(gbDevice, day); } private String getHM(long value) { @@ -117,16 +115,17 @@ public class Widget extends AppWidgetProvider { PendingIntent startChartsPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT, false); views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent); - long[] dailyTotals = getSteps(deviceForWidget); - int steps = (int) dailyTotals[0]; - int sleep = (int) dailyTotals[1]; + DailyTotals dailyTotals = getSteps(deviceForWidget); + int steps = (int) dailyTotals.getSteps(); + int sleep = (int) dailyTotals.getSleep(); + int distanceCm = (int) dailyTotals.getDistance(); ActivityUser activityUser = new ActivityUser(); int stepGoal = activityUser.getStepsGoal(); int sleepGoal = activityUser.getSleepDurationGoal(); int sleepGoalMinutes = sleepGoal * 60; int distanceGoal = activityUser.getDistanceGoalMeters() * 100; int stepLength = activityUser.getStepLengthCm(); - double distanceMeters = dailyTotals[0] * stepLength * 0.01; + double distanceMeters = (distanceCm > 0 ? distanceCm : steps * stepLength) * 0.01; String distanceFormatted = FormatUtils.getFormattedDistanceLabel(distanceMeters); if (sleep < 1) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java index 66e10f36f..726fea73e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DevicesFragment.java @@ -66,7 +66,7 @@ public class DevicesFragment extends Fragment { private RecyclerView deviceListView; private FloatingActionButton fab; List deviceList; - private HashMap deviceActivityHashMap = new HashMap(); + private HashMap deviceActivityHashMap = new HashMap(); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -219,11 +219,10 @@ public class DevicesFragment extends Fragment { super.onDestroy(); } - private long[] getSteps(GBDevice device, DBHandler db) { + private DailyTotals getSteps(GBDevice device, DBHandler db) { Calendar day = GregorianCalendar.getInstance(); - DailyTotals ds = new DailyTotals(); - return ds.getDailyTotalsForDevice(device, day, db); + return DailyTotals.getDailyTotalsForDevice(device, day, db); } public void refreshPairedDevices() { @@ -266,7 +265,7 @@ public class DevicesFragment extends Fragment { final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator(); final boolean showActivityCard = GBApplication.getDevicePrefs(gbDevice).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD, true); if (coordinator.supportsActivityTracking() && showActivityCard) { - final long[] stepsAndSleepData = getSteps(gbDevice, db); + final DailyTotals stepsAndSleepData = getSteps(gbDevice, db); deviceActivityHashMap.put(gbDevice.getAddress(), stepsAndSleepData); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java index 9a63e8fe5..000177ee8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java @@ -75,6 +75,11 @@ public class ActivityAnalysis { amount.addSteps(steps); } + final int distance = sample.getDistanceCm(); + if (distance >= 0) { + amount.addDistance(distance); + } + if (previousSample != null) { long timeDifference = sample.getTimestamp() - previousSample.getTimestamp(); if (previousSample.getRawKind() == sample.getRawKind()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java index 723542239..c35e8c9b8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java @@ -35,7 +35,7 @@ public class StepAnalysis { private int totalDailySteps = 0; public List calculateStepSessions(List samples) { - LOG.debug("get all samples activitysessions: " + samples.toArray().length); + LOG.debug("get all samples activity sessions: {}", samples.size()); List result = new ArrayList<>(); ActivityUser activityUser = new ActivityUser(); final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5); @@ -50,7 +50,9 @@ public class StepAnalysis { Date sessionStart = null; Date sessionEnd; int activeSteps = 0; //steps that we count + int activeDistanceCm = 0; int stepsBetweenActivePeriods = 0; //steps during time when we maybe take a rest but then restart + int distanceBetweenActivePeriods = 0; int durationSinceLastActiveStep = 0; ActivityKind activityKind; @@ -77,7 +79,18 @@ public class StepAnalysis { if (sessionStart == null) { sessionStart = getDateFromSample(sample); - activeSteps = sample.getSteps(); + if (sample.getSteps() >= 0) { + activeSteps = sample.getSteps(); + } else { + activeSteps = 0; + } + if (sample.getDistanceCm() >= 0) { + activeDistanceCm = sample.getDistanceCm(); + } else if (activeSteps > 0) { + activeDistanceCm = activeSteps * stepLengthCm; + } else { + activeDistanceCm = 0; + } activeIntensity = sample.getIntensity(); heartRateSum = new ArrayList<>(); if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) { @@ -85,6 +98,7 @@ public class StepAnalysis { } durationSinceLastActiveStep = 0; stepsBetweenActivePeriods = 0; + distanceBetweenActivePeriods = 0; heartRateBetweenActivePeriodsSum = new ArrayList<>(); previousSample = null; } @@ -94,6 +108,11 @@ public class StepAnalysis { if (sample.getSteps() > MIN_STEPS_PER_MINUTE || //either some steps (sample.getIntensity() > MIN_SESSION_INTENSITY && sample.getSteps() > 0)) { //or some intensity plus at least one step activeSteps += sample.getSteps() + stepsBetweenActivePeriods; + if (sample.getDistanceCm() >= 0) { + activeDistanceCm += sample.getDistanceCm() + distanceBetweenActivePeriods; + } else { + activeDistanceCm += sample.getSteps() * stepLengthCm + distanceBetweenActivePeriods; + } activeIntensity += sample.getIntensity() + intensityBetweenActivePeriods; if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) { heartRateSum.add(sample.getHeartRate()); @@ -101,11 +120,19 @@ public class StepAnalysis { heartRateSum.addAll(heartRateBetweenActivePeriodsSum); heartRateBetweenActivePeriodsSum = new ArrayList<>(); stepsBetweenActivePeriods = 0; + distanceBetweenActivePeriods = 0; intensityBetweenActivePeriods = 0; durationSinceLastActiveStep = 0; } else { //short break data to remember, we will add it to the rest later, if break not too long - stepsBetweenActivePeriods += sample.getSteps(); + if (sample.getSteps() >= 0) { + stepsBetweenActivePeriods += sample.getSteps(); + } + if (sample.getDistanceCm() >= 0) { + distanceBetweenActivePeriods += sample.getDistanceCm(); + } else if (sample.getSteps() > 0) { + distanceBetweenActivePeriods += sample.getSteps() * stepLengthCm; + } if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) { heartRateBetweenActivePeriodsSum.add(sample.getHeartRate()); } @@ -120,7 +147,7 @@ public class StepAnalysis { if (session_length >= MIN_SESSION_LENGTH) { //valid activity session int heartRateAverage = heartRateSum.toArray().length > 0 ? calculateSumOfInts(heartRateSum) / heartRateSum.toArray().length : 0; - float distance = (float) (activeSteps * STEP_LENGTH_M); + float distance = activeDistanceCm * 0.01f; sessionEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L); activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity); ActivitySession activitySession = new ActivitySession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind); @@ -135,14 +162,14 @@ public class StepAnalysis { } //trailing activity: make sure we show the last portion of the data as well in case no further activity is recorded yet - if (sessionStart != null && previousSample != null) { + if (sessionStart != null) { int current = previousSample.getTimestamp(); int starting = (int) (sessionStart.getTime() / 1000); int session_length = current - starting - durationSinceLastActiveStep; if (session_length >= MIN_SESSION_LENGTH) { int heartRateAverage = heartRateSum.toArray().length > 0 ? calculateSumOfInts(heartRateSum) / heartRateSum.toArray().length : 0; - float distance = (float) (activeSteps * STEP_LENGTH_M); + float distance = activeDistanceCm * 0.01f; sessionEnd = getDateFromSample(previousSample); activityKind = detect_activity_kind(session_length, activeSteps, heartRateAverage, activeIntensity); ActivitySession ongoingActivity = new ActivitySession(sessionStart, sessionEnd, activeSteps, heartRateAverage, activeIntensity, distance, activityKind); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java index 28be1ea2e..39f138ee8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepStreaksDashboard.java @@ -291,8 +291,7 @@ public class StepStreaksDashboard extends MaterialDialogFragment { int all_steps = 0; int firstDataTimestamp = 0; - DailyTotals dailyTotals = new DailyTotals(); - ActivitySample firstSample = dailyTotals.getFirstSample(db, device); + ActivitySample firstSample = DailyTotals.getFirstSample(db, device); if (firstSample == null) { //no data at all return; } @@ -306,8 +305,8 @@ public class StepStreaksDashboard extends MaterialDialogFragment { break; } - long[] daily_data = dailyTotals.getDailyTotalsForDevice(device, day, db); - int steps_this_day = (int) daily_data[0]; + DailyTotals daily_data = DailyTotals.getDailyTotalsForDevice(device, day, db); + int steps_this_day = (int) daily_data.getSteps(); if (steps_this_day > 0) { all_step_days++; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java index bc73a8e38..c3dfba445 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepsFragment.java @@ -51,20 +51,25 @@ abstract class StepsFragment extends AbstractChartFragment List daysData = new ArrayList<>();; for (int counter = 0; counter < TOTAL_DAYS; counter++) { long totalSteps = 0; + long totalDistance = 0; ActivityAmounts amounts = getActivityAmountsForDay(db, day, device); for (ActivityAmount amount : amounts.getAmounts()) { if (amount.getTotalSteps() > 0) { totalSteps += amount.getTotalSteps(); } + if (amount.getTotalDistance() > 0) { + totalDistance += amount.getTotalDistance(); + } } - double distance = 0; - if (totalSteps > 0) { + double distance = totalDistance; + if (totalDistance == 0 && totalSteps > 0) { + // For gadgets that do not report distance, compute it from the steps ActivityUser activityUser = new ActivityUser(); int stepLength = activityUser.getStepLengthCm(); - distance = ((stepLength * 1.0 / 100) * totalSteps) / 1000; + distance = stepLength * totalSteps; } Calendar d = (Calendar) day.clone(); - daysData.add(new StepsDay(d, totalSteps, distance)); + daysData.add(new StepsDay(d, totalSteps, distance / 100_000)); day.add(Calendar.DATE, 1); } return daysData; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java index dbd260592..5a9d09481 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java @@ -122,6 +122,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceFolder; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; +import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; @@ -143,10 +144,10 @@ public class GBDeviceAdapterv2 extends ListAdapter deviceActivityMap = new HashMap(); + private HashMap deviceActivityMap = new HashMap<>(); private final StableIdGenerator idGenerator = new StableIdGenerator(); - public GBDeviceAdapterv2(Context context, List deviceList, HashMap deviceMap) { + public GBDeviceAdapterv2(Context context, List deviceList, HashMap deviceMap) { super(new GBDeviceDiffUtil()); this.context = context; this.deviceList = deviceList; @@ -304,7 +305,7 @@ public class GBDeviceAdapterv2 extends ListAdapter 0 ? distanceCm : steps * stepLength) * 0.01; String distanceFormatted = FormatUtils.getFormattedDistanceLabel(distanceMeters); setUpChart(holder.TotalStepsChart); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java index 64f1a0e22..63eeb7fac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java @@ -28,6 +28,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -197,7 +198,10 @@ public abstract class AbstractSampleProvider i // Steps on the Garmin Watch are reported cumulatively per day - convert them to // This slightly breaks activity recognition, because we don't have per-minute granularity... int prevSteps = samples.get(0).getSteps(); + int prevDistance = samples.get(0).getDistanceCm(); + int prevActiveCalories = samples.get(0).getActiveCalories(); samples.get(0).setTimestamp((samples.get(0).getTimestamp() / 60) * 60); + int bak; for (int i = 1; i < samples.size(); i++) { final T s1 = samples.get(i - 1); @@ -207,11 +211,26 @@ public abstract class AbstractSampleProvider i if (!sameDay(s1, s2)) { // went past midnight - reset steps prevSteps = s2.getSteps() > 0 ? s2.getSteps() : 0; - } else if (s2.getSteps() > 0) { - // New steps sample for the current day - subtract the previous seen sample - int bak = s2.getSteps(); - s2.setSteps(s2.getSteps() - prevSteps); - prevSteps = bak; + prevDistance = s2.getDistanceCm() > 0 ? s2.getDistanceCm() : 0; + prevActiveCalories = s2.getActiveCalories() > 0 ? s2.getActiveCalories() : 0; + } else { + // New value for the current day - subtract the previous seen sample + + if (s2.getSteps() > 0) { + bak = s2.getSteps(); + s2.setSteps(s2.getSteps() - prevSteps); + prevSteps = bak; + } + if (s2.getDistanceCm() > 0) { + bak = s2.getDistanceCm(); + s2.setDistanceCm(s2.getDistanceCm() - prevDistance); + prevDistance = bak; + } + if (s2.getActiveCalories() > 0) { + bak = s2.getActiveCalories(); + s2.setActiveCalories(s2.getActiveCalories() - prevActiveCalories); + prevActiveCalories = bak; + } } } } @@ -255,7 +274,7 @@ public abstract class AbstractSampleProvider i final long nanoStart = System.nanoTime(); - final List ret = new ArrayList<>(samples); + final List ret = new LinkedList<>(samples); //ret.sort(Comparator.comparingLong(T::getTimestamp)); @@ -263,13 +282,7 @@ public abstract class AbstractSampleProvider i if (firstTimestamp - timestamp_from > 60) { // Gap at the start for (int ts = timestamp_from; ts <= firstTimestamp + 60; ts += 60) { - final T dummySample = createActivitySample(); - dummySample.setTimestamp(ts); - dummySample.setRawKind(ActivityKind.UNKNOWN.getCode()); - dummySample.setRawIntensity(ActivitySample.NOT_MEASURED); - dummySample.setSteps(ActivitySample.NOT_MEASURED); - dummySample.setProvider(this); - ret.add(0, dummySample); + ret.add(0, createDummySample(ts)); } } @@ -279,13 +292,7 @@ public abstract class AbstractSampleProvider i if (minTo - lastTimestamp > 60) { // Gap at the end for (int ts = lastTimestamp + 60; ts <= minTo; ts += 60) { - final T dummySample = createActivitySample(); - dummySample.setTimestamp(ts); - dummySample.setRawKind(ActivityKind.UNKNOWN.getCode()); - dummySample.setRawIntensity(ActivitySample.NOT_MEASURED); - dummySample.setSteps(ActivitySample.NOT_MEASURED); - dummySample.setProvider(this); - ret.add(dummySample); + ret.add(createDummySample(ts)); } } @@ -297,13 +304,7 @@ public abstract class AbstractSampleProvider i if (sample.getTimestamp() - previousSample.getTimestamp() > 60) { LOG.trace("Filling gap between {} and {}", Instant.ofEpochSecond(previousSample.getTimestamp() + 60), Instant.ofEpochSecond(sample.getTimestamp())); for (int ts = previousSample.getTimestamp() + 60; ts < sample.getTimestamp(); ts += 60) { - final T dummySample = createActivitySample(); - dummySample.setTimestamp(ts); - dummySample.setRawKind(ActivityKind.UNKNOWN.getCode()); - dummySample.setRawIntensity(ActivitySample.NOT_MEASURED); - dummySample.setSteps(ActivitySample.NOT_MEASURED); - dummySample.setProvider(this); - it.add(dummySample); + it.add(createDummySample(ts)); } } previousSample = sample; @@ -318,4 +319,17 @@ public abstract class AbstractSampleProvider i return ret; } + + private T createDummySample(final int ts) { + final T dummySample = createActivitySample(); + dummySample.setTimestamp(ts); + dummySample.setRawKind(ActivityKind.UNKNOWN.getCode()); + dummySample.setRawIntensity(ActivitySample.NOT_MEASURED); + dummySample.setSteps(ActivitySample.NOT_MEASURED); + dummySample.setHeartRate(ActivitySample.NOT_MEASURED); + dummySample.setDistanceCm(ActivitySample.NOT_MEASURED); + dummySample.setActiveCalories(ActivitySample.NOT_MEASURED); + dummySample.setProvider(this); + return dummySample; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java index 3f9d37087..7f6d1886f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestSampleProvider.java @@ -20,8 +20,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.apache.commons.lang3.ArrayUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Calendar; @@ -39,8 +37,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; public class TestSampleProvider extends AbstractSampleProvider { - private static final Logger LOG = LoggerFactory.getLogger(TestSampleProvider.class); - public TestSampleProvider(final GBDevice device, final DaoSession session) { super(device, session); } @@ -59,12 +55,14 @@ public class TestSampleProvider extends AbstractSampleProvider. */ package nodomain.freeyourgadget.gadgetbridge.entities; +import androidx.annotation.NonNull; + import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -29,7 +31,7 @@ public abstract class AbstractActivitySample implements ActivitySample { return mProvider; } - public void setProvider(SampleProvider provider) { + public void setProvider(SampleProvider provider) { mProvider = provider; } @@ -57,6 +59,12 @@ public abstract class AbstractActivitySample implements ActivitySample { public void setSteps(int steps) { } + public void setDistanceCm(int distance) { + } + + public void setActiveCalories(int activeCalories) { + } + /** * Unix timestamp of the sample, i.e. the number of seconds since 1970-01-01 00:00:00 UTC. */ @@ -89,6 +97,17 @@ public abstract class AbstractActivitySample implements ActivitySample { return NOT_MEASURED; } + @Override + public int getDistanceCm() { + return NOT_MEASURED; + } + + @Override + public int getActiveCalories() { + return NOT_MEASURED; + } + + @NonNull @Override public String toString() { ActivityKind kind = getProvider() != null ? getKind() : ActivityKind.NOT_MEASURED; @@ -97,7 +116,9 @@ public abstract class AbstractActivitySample implements ActivitySample { "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(getTimestamp())) + ", intensity=" + intensity + ", steps=" + getSteps() + - ", heartrate=" + getHeartRate() + + ", distanceCm=" + getDistanceCm() + + ", activeCalories=" + getActiveCalories() + + ", heartRate=" + getHeartRate() + ", type=" + kind + ", userId=" + getUserId() + ", deviceId=" + getDeviceId() + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java index c49f88ed7..30da7d0c0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java @@ -23,6 +23,7 @@ public class ActivityAmount { private short percent; private long totalSeconds; private long totalSteps; + private long totalDistance; private Date startDate = null; private Date endDate = null; @@ -38,6 +39,10 @@ public class ActivityAmount { totalSteps += steps; } + public void addDistance(long distance) { + totalDistance += distance; + } + public long getTotalSeconds() { return totalSeconds; } @@ -46,6 +51,10 @@ public class ActivityAmount { return totalSteps; } + public long getTotalDistance() { + return totalDistance; + } + public ActivityKind getActivityKind() { return activityKind; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java index 4c55fa7f4..b5c6d76dd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySample.java @@ -72,6 +72,16 @@ public interface ActivitySample extends TimeStamped { */ int getSteps(); + /** + * Returns the distance moved during the period of this sample, in cm. -1 if unknown. + */ + int getDistanceCm(); + + /** + * Returns the calories burned during the period of this sample, in kcal. -1 if unknown. + */ + int getActiveCalories(); + /** * Returns the heart rate measured at the corresponding timestamp. * The value is returned in heart beats per minute, in the range from diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java index 79e3dd013..4483251b2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DailyTotals.java @@ -19,9 +19,12 @@ package nodomain.freeyourgadget.gadgetbridge.model; import android.content.Context; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.Arrays; import java.util.Calendar; import java.util.List; @@ -34,47 +37,46 @@ import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class DailyTotals { +public class DailyTotals implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(DailyTotals.class); + private final long steps; + private final long distance; + private final long[] sleep; // light deep rem awake - public long[] getDailyTotalsForAllDevices(Calendar day) { - Context context = GBApplication.getContext(); - //get today's steps for all devices in GB - long all_steps = 0; - long all_sleep = 0; - - if (context instanceof GBApplication) { - GBApplication gbApp = (GBApplication) context; - List devices = gbApp.getDeviceManager().getDevices(); - for (GBDevice device : devices) { - DeviceCoordinator coordinator = device.getDeviceCoordinator(); - if (!coordinator.supportsActivityDataFetching() && !coordinator.supportsActivityTracking()) { - continue; - } - long[] all_daily = getDailyTotalsForDevice(device, day); - all_steps += all_daily[0]; - all_sleep += all_daily[1]; - } - } - //LOG.debug("gbwidget daily totals, all steps:" + all_steps); - //LOG.debug("gbwidget daily totals, all sleep:" + all_sleep); - return new long[]{all_steps, all_sleep}; + public DailyTotals() { + this(0, 0, new long[]{0, 0, 0 ,0}); } + public DailyTotals(final long steps, final long distance, final long[] sleep) { + this.steps = steps; + this.distance = distance; + this.sleep = sleep; + } - public long[] getDailyTotalsForDevice(GBDevice device, Calendar day) { + public long getSteps() { + return steps; + } + + public long getDistance() { + return distance; + } + + public long getSleep() { + return (long) Arrays.stream(sleep).asDoubleStream().sum(); + } + + public static DailyTotals getDailyTotalsForDevice(GBDevice device, Calendar day) { try (DBHandler handler = GBApplication.acquireDB()) { return getDailyTotalsForDevice(device, day, handler); - } catch (Exception e) { //GB.toast("Error loading sleep/steps widget data for device: " + device, Toast.LENGTH_SHORT, GB.ERROR, e); - return new long[]{0, 0}; + return new DailyTotals(); } } - public long[] getDailyTotalsForDevice(GBDevice device, Calendar day, DBHandler handler) { + public static DailyTotals getDailyTotalsForDevice(GBDevice device, Calendar day, DBHandler handler) { ActivityAnalysis analysis = new ActivityAnalysis(); ActivityAmounts amountsSteps; ActivityAmounts amountsSleep; @@ -83,13 +85,13 @@ public class DailyTotals { amountsSleep = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, -12, device)); long[] sleep = getTotalsSleepForActivityAmounts(amountsSleep); - long steps = getTotalsStepsForActivityAmounts(amountsSteps); + Pair stepsDistance = getTotalsStepsForActivityAmounts(amountsSteps); // Purposely not including awake sleep - return new long[]{steps, sleep[0] + sleep[1] + sleep[2]}; + return new DailyTotals(stepsDistance.getLeft(), stepsDistance.getRight(), sleep); } - private long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) { + private static long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) { long totalSecondsDeepSleep = 0; long totalSecondsLightSleep = 0; long totalSecondsRemSleep = 0; @@ -109,21 +111,21 @@ public class DailyTotals { long totalMinutesLightSleep = (totalSecondsLightSleep / 60); long totalMinutesRemSleep = (totalSecondsRemSleep / 60); long totalMinutesAwakeSleep = (totalSecondsAwakeSleep / 60); - return new long[]{totalMinutesDeepSleep, totalMinutesLightSleep, totalMinutesRemSleep, totalMinutesAwakeSleep}; + return new long[]{totalMinutesLightSleep, totalMinutesDeepSleep, totalMinutesRemSleep, totalMinutesAwakeSleep}; } - - public long getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) { + public static Pair getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) { long totalSteps = 0; + long totalDistance = 0; for (ActivityAmount amount : activityAmounts.getAmounts()) { totalSteps += amount.getTotalSteps(); + totalDistance += amount.getTotalDistance(); } - return totalSteps; + return Pair.of(totalSteps, totalDistance); } - - private List getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) { + private static List getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) { int startTs; int endTs; @@ -139,23 +141,21 @@ public class DailyTotals { return getSamples(db, device, startTs, endTs); } - - public List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + public static List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { return getAllSamples(db, device, tsFrom, tsTo); } - - protected SampleProvider getProvider(DBHandler db, GBDevice device) { + protected static SampleProvider getProvider(DBHandler db, GBDevice device) { DeviceCoordinator coordinator = device.getDeviceCoordinator(); return coordinator.getSampleProvider(device, db.getDaoSession()); } - protected List getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + protected static List getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { SampleProvider provider = getProvider(db, device); return provider.getAllActivitySamples(tsFrom, tsTo); } - public ActivitySample getFirstSample(DBHandler db, GBDevice device) { + public static ActivitySample getFirstSample(DBHandler db, GBDevice device) { SampleProvider provider = getProvider(db, device); return provider.getFirstActivitySample(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java index a1ab4dd62..2cf478ca2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DashboardUtils.java @@ -42,11 +42,10 @@ import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals; public class DashboardUtils { private static final Logger LOG = LoggerFactory.getLogger(DashboardUtils.class); - public static long getSteps(GBDevice device, DBHandler db, int timeTo) { + public static DailyTotals getDailyTotals(GBDevice device, DBHandler db, int timeTo) { Calendar day = GregorianCalendar.getInstance(); day.setTimeInMillis(timeTo * 1000L); - DailyTotals ds = new DailyTotals(); - return ds.getDailyTotalsForDevice(device, day, db)[0]; + return DailyTotals.getDailyTotalsForDevice(device, day, db); } public static int getStepsTotal(DashboardFragment.DashboardData dashboardData) { @@ -55,7 +54,7 @@ public class DashboardUtils { try (DBHandler dbHandler = GBApplication.acquireDB()) { for (GBDevice dev : devices) { if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) { - totalSteps += getSteps(dev, dbHandler, dashboardData.timeTo); + totalSteps += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getSteps(); } } } catch (Exception e) { @@ -76,8 +75,7 @@ public class DashboardUtils { public static long getSleep(GBDevice device, DBHandler db, int timeTo) { Calendar day = GregorianCalendar.getInstance(); day.setTimeInMillis(timeTo * 1000L); - DailyTotals ds = new DailyTotals(); - return ds.getDailyTotalsForDevice(device, day, db)[1]; + return DailyTotals.getDailyTotalsForDevice(device, day, db).getSleep(); } public static long getSleepMinutesTotal(DashboardFragment.DashboardData dashboardData) { @@ -105,20 +103,26 @@ public class DashboardUtils { } public static float getDistanceTotal(DashboardFragment.DashboardData dashboardData) { + ActivityUser activityUser = new ActivityUser(); + int stepLength = activityUser.getStepLengthCm(); + List devices = GBApplication.app().getDeviceManager().getDevices(); - long totalSteps = 0; + long totalDistanceCm = 0; try (DBHandler dbHandler = GBApplication.acquireDB()) { for (GBDevice dev : devices) { if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking()) { - totalSteps += getSteps(dev, dbHandler, dashboardData.timeTo); + final DailyTotals dailyTotals = getDailyTotals(dev, dbHandler, dashboardData.timeTo); + if (dailyTotals.getSteps() > 0 && dailyTotals.getDistance() > 0) { + totalDistanceCm += dailyTotals.getDistance(); + } else { + totalDistanceCm += dailyTotals.getSteps() * stepLength; + } } } } catch (Exception e) { LOG.warn("Could not calculate total distance: ", e); } - ActivityUser activityUser = new ActivityUser(); - int stepLength = activityUser.getStepLengthCm(); - return totalSteps * stepLength * 0.01f; + return totalDistanceCm * 0.01f; } public static float getDistanceGoalFactor(DashboardFragment.DashboardData dashboardData) {