mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Health Connect: Harden sync-start selection against non-positive sample timestamps
When determining the Health Connect sync start point for activity-sample based data types (ACTIVITY, SLEEP), getFirstSampleTimestamp inspects only the single oldest row and discards it if its timestamp is not positive. If that oldest row has a 0 or otherwise non-positive timestamp, the start point is null and sync is skipped for every data type backed by that table, while data types on separate tables keep syncing. Add a getFirstActivitySample(after) overload returning the oldest sample with a timestamp strictly greater than the given limit, and use it with 0 so an invalid head row no longer disables start-point determination.
This commit is contained in:
+23
@@ -172,6 +172,29 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
|
||||
return sample;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public T getFirstActivitySample(final int after) {
|
||||
QueryBuilder<T> 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<T> 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
|
||||
|
||||
@@ -119,4 +119,11 @@ public interface SampleProvider<T extends AbstractActivitySample> {
|
||||
@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);
|
||||
|
||||
}
|
||||
|
||||
+6
@@ -106,6 +106,12 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractActivitySample getFirstActivitySample(final int after) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public UnknownDeviceCoordinator() {
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+6
@@ -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<GenericActivitySample>, timestampFrom: Int, timestampTo: Int) {
|
||||
val stagesMap = RangeMap<Long, ActivityKind?>(RangeMap.Mode.LOWER_BOUND)
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+26
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user