Replace client-side takeIf filter with a proper DB query using
Timestamp.gt(after) so that the first valid sample is found even
when the absolute oldest row has a non-positive timestamp.
When BT was turned off while a device was mid-connect, BtBRQueue would
end up in WAITING_FOR_RECONNECT even though the queue had been disposed,
causing AutoConnectIntervalReceiver to schedule reconnects even while
bluetooth is already off.
- (de-)activate via Settings / Developer Options / Experimental metrics UI
- UI currently uses this only for TrainingAcuteLoad, TrainingChronicLoad and RestingMetabolicRate
- Garmin devices are the only ones that actually record these values in the database
- fix: use milliseconds instead of seconds to align with most other samples
- DB upgrade copies existing data from GARMIN_RESTING_METABOLIC_RATE_SAMPLE, GENERIC_TRAINING_LOAD_ACUTE_SAMPLE and GENERIC_TRAINING_LOAD_CHRONIC_SAMPLE to GENERIC_METRIC_SAMPLE
- new data is saved to both the specific table as well as GENERIC_METRIC_SAMPLE
- add GenericMetricSampleProvider with service functions
- add GENERIC_MAXIMUM_OXYGEN_UPTAKE parsing for Garmin
LOG.atLevel(logLevel).log(...) in NativeFITMessage.getFieldDefinition
uses the SLF4J 2.0 fluent API, which logback-android 3.0.0 does not
implement. This throws AbstractMethodError at runtime, crashing the
FitAsyncProcessor thread and aborting the entire Garmin device sync
when a .FIT file triggers the type/size mismatch path.
Replace with a switch dispatching to LOG.debug/info/warn.
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.
Health Connect enforces hard min/max bounds on each record-type field; a
single out-of-range value caused the record constructor to throw and
killed the entire batch insert. The trigger was a Garmin Venu 3
producing HRV > 200 ms, but several syncers shared the same gap (no
upper bound, only > 0 or no validation at all).
Add explicit per-syncer numeric guards matching the bounds enforced by
HC's record constructors, drop out-of-range samples, log them with the
existing [HC_SYNC] prefix, and continue the sync.
Bounds applied (per-record HC enforcement):
- HRV: 1..200 ms
- VO2Max: 0..100 ml/kg/min
- Weight: 0..1000 kg
- BloodGlucose: 0..900.91 mg/dL (= 50 mmol/L) -- previously unvalidated
- RespiratoryRate: 0..1000 breaths/min
- HeartRate / RestingHR: 1..300 / 0..300 bpm (widened from 20..250 to
match HC's contract; downstream consumers can apply physiological
filters)
- SpO2: 0..100 %
- Steps: 1..1000000 per record
- ActiveCalories: 0..1000000 kcal per record
- Distance: 0..1000000 m per record
- Skin temperature delta: -30..30 °C
- Body temperature: existing physiological clamp 25..45 °C kept (HC has
no enforced bound)
Add Logger.skipOutOfRange extension in syncers/SyncerLogging.kt and
relax HC_SYNC_TAG visibility from private to internal so syncers can
share it.
HeartRateSync aggregates dropped-sample counts into one summary log
line per slice rather than per-sample, to avoid log spam from glitchy
firmware.
convertSample visibility relaxed from protected to internal in the two
abstract base syncers so the new boundary tests can call it directly.
30 new unit tests in SyncerRangeValidationTest cover boundary
conditions per syncer.