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 6e39fbe01f..1202efc0e0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java @@ -172,6 +172,29 @@ public abstract class AbstractSampleProvider i return sample; } + @Nullable + @Override + public T getFirstActivitySample(final int after) { + QueryBuilder qb = getSampleDao().queryBuilder(); + Device dbDevice = DBHelper.findDevice(getDevice(), getSession()); + if (dbDevice == null) { + // no device, no sample + return null; + } + Property deviceProperty = getDeviceIdentifierSampleProperty(); + Property timestampProperty = getTimestampSampleProperty(); + qb.where(timestampProperty.gt(after)) + .where(deviceProperty.eq(dbDevice.getId())) + .orderAsc(timestampProperty).limit(1); + List samples = qb.build().list(); + if (samples.isEmpty()) { + return null; + } + T sample = samples.get(0); + sample.setProvider(this); + return sample; + } + /** * Get the activity samples between two timestamps (inclusive). Exactly one every minute. * @param timestamp_from Start timestamp diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java index e35e6cd481..a5e1f64a2e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java @@ -119,4 +119,11 @@ public interface SampleProvider { @Nullable T getFirstActivitySample(); + /** + * Returns the activity sample with the oldest timestamp strictly after the given limit, or null if none + * @return the oldest sample after the limit or null + */ + @Nullable + T getFirstActivitySample(int after); + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java index 5a5814a4a6..f9a2cb4a60 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java @@ -106,6 +106,12 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator { return null; } + @Nullable + @Override + public AbstractActivitySample getFirstActivitySample(final int after) { + return null; + } + } public UnknownDeviceCoordinator() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/generic_hr/GenericHeartRateActivitySampleProvider.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/generic_hr/GenericHeartRateActivitySampleProvider.kt index de0154a7a3..8b498620e5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/generic_hr/GenericHeartRateActivitySampleProvider.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/generic_hr/GenericHeartRateActivitySampleProvider.kt @@ -88,6 +88,10 @@ open class GenericHeartRateActivitySampleProvider(device: GBDevice, session: Dao return heartRateProvider.firstSample?.let { hrSample -> toGenericActivitySample(hrSample) } } + override fun getFirstActivitySample(after: Int): GenericActivitySample? { + return getFirstActivitySample()?.takeIf { it.timestamp > after } + } + private fun toGenericActivitySample(hrSample: GenericHeartRateSample): GenericActivitySample { val activitySample = GenericActivitySample() activitySample.provider = this diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/gloryfit/GloryFitActivitySampleProvider.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/gloryfit/GloryFitActivitySampleProvider.kt index 433babdad6..cb10cddcd5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/gloryfit/GloryFitActivitySampleProvider.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/gloryfit/GloryFitActivitySampleProvider.kt @@ -136,6 +136,12 @@ open class GloryFitActivitySampleProvider(device: GBDevice, session: DaoSession) return null } + override fun getFirstActivitySample(after: Int): GenericActivitySample? { + // TODO getFirstActivitySample + LOG.warn("getFirstActivitySample(after) not implemented"); + return null + } + fun overlaySleep(samples: MutableList, timestampFrom: Int, timestampTo: Int) { val stagesMap = RangeMap(RangeMap.Mode.LOWER_BOUND) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/healthconnect/HealthConnectUtils.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/healthconnect/HealthConnectUtils.kt index 2c59b2c8c1..ef3943fe8d 100755 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/healthconnect/HealthConnectUtils.kt +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/healthconnect/HealthConnectUtils.kt @@ -686,7 +686,7 @@ class HealthConnectUtils { provider.firstSample?.timestamp?.takeIf { it > 0 }?.let { Instant.ofEpochMilli(it) } } is SampleProvider<*> -> { // For ActivitySample based providers - provider.firstActivitySample?.timestamp?.takeIf { it > 0 }?.let { Instant.ofEpochSecond(it.toLong()) } + provider.getFirstActivitySample(0)?.timestamp?.takeIf { it > 0 }?.let { Instant.ofEpochSecond(it.toLong()) } } is BaseActivitySummaryDao -> { val deviceEntity = DBHelper.getDevice(device, db.daoSession) ?: return null diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java index 648243cb97..76676f2484 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java @@ -18,6 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.test.TestBase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -169,6 +170,31 @@ public class SampleProviderTest extends TestBase { // FIXME assertEquals(1, activitySamples.size()); } + @Test + public void testGetFirstActivitySampleSkipsNonPositiveTimestamp() { + MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession); + User user = DBHelper.getUser(daoSession); + Device device = DBHelper.getDevice(dummyGBDevice, daoSession); + + MiBandActivitySample zero = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 0, 10, 70, 1000, user, device); + MiBandActivitySample valid = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 100, 20, 80, 1030, user, device); + sampleProvider.addGBActivitySamples(new MiBandActivitySample[] { zero, valid }); + + // oldest row overall is the timestamp 0 row + assertEquals(0, sampleProvider.getFirstActivitySample().getTimestamp()); + + // but the overload skips it and returns the first row with timestamp > 0 + MiBandActivitySample first = sampleProvider.getFirstActivitySample(0); + assertNotNull(first); + assertEquals(100, first.getTimestamp()); + } + + @Test + public void testGetFirstActivitySampleNoSamples() { + MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession); + assertNull(sampleProvider.getFirstActivitySample(0)); + } + @Test public void testHighResSamples() { // Mi Band sample provider does not support this at the moment, so we use the Huawei sample provider