The start point derived from the oldest activity sample was guarded only by
timestamp > 0. A bogus near-epoch sample timestamp passes that guard and
resolves the start to ~1970, triggering a full historical resync. Floor it at
2015-01-01; Gadgetbridge predates that, so any earlier sample is not real
device data. Reuses the indexed getFirstActivitySample(after) query so the
bogus head rows are skipped at the DB level.
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 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.
ExerciseRoute requires strictly increasing timestamps; duplicate-second
points from device GPS parsers (e.g. Xiaomi Smart Band 9 Pro outdoor
cycling) caused IllegalArgumentException at construction time, aborting
the workout sync entirely.
Extract route construction into buildSanitisedRoute, which:
- drops points outside the workout time window
- drops points with non-finite or out-of-range lat/lng (HC's Location
constructor enforces lat in [-90, 90], lng in [-180, 180])
- drops points with non-finite or negative hdop/vdop accuracy (HC
rejects negative accuracy)
- deduplicates points by Instant
- returns null if fewer than two usable points remain
- catches IllegalArgumentException as a safety net so future HC
invariants degrade gracefully (workout saves without route rather
than the whole sync aborting)
Logs dropped points with the [HC_SYNC] prefix so users can grep.
The Androidx annotations are frequently used whereas Jetbrains annotations are infrequently used.
Where possible, use Androidx instead of Jetbrains annotations.
Adds the capability to set and dismiss alarms from third party apps through an Intent API, fixes#5553
**This PR was co-authored by ChatGPT and José Rebelo (passively):**
- Scaffold for the core component `DeviceAlarmReceiver` was taken from José's `DeviceSettingsReceiver`
- The core implementation was written by me by hand, ChatGPT was used to review and find potential issues and bugs
- The `DeviceAlarmReceiverTest` unit tests were written by ChatGPT and reviewed by me
**About these changes:**
- Adds a new developer settings entry for the device that the user needs to enable, so other apps can use the Intent API for such device
- Supports app blacklisting as it is done in the `DeviceSettingsReceiver`
- `ConfigureAlarms.addMissingAlarms()` was migrated to `DBHelper.fillMissingAlarms()` to provide this abstraction globally
- `AlarmReceiver` was migrated to `SunriseSunsetAlarmReceiver` to avoid ambiguity with this feature; the formerly called `AlarmReceiver` was related to setting calendar events based on the sunrise/sunset via an Intent API
- The API is registered in the `DeviceCommunicationService`
- The `ConfigureAlarms` activity updates its list after setting alarms from a third party app by broadcasting `ACTION_SAVE_ALARMS`
- It is possible to set alarms of a device that is offline, it should sync when the device connects next time, unless it downloads the alarms from the device when connecting
- However, this device-to-GadgetBridge alarm sync API is not commonly abstracted, so we cannot respect existing alarms on the devices when changing alarms within the new Intent API; the database is the single source of truth and connecting the device after using the API will reset alarms
- ~~The API was designed to align with the `AlarmClock` API from Android, but~~ there is no feature parity to improve the design and interaction; after all the `AlarmClock` API is interacted with `startActivity`, but `GadgetBridge` uses `sendBroadcast` receivers, which is recommend for background tasks
- Errors are not raised to the caller (i.e. providing unknown or invalid Mac Address of device or trying to set an alarm on a nonexisting slot); these errors are logged though
**EDIT: Additional changes during the development:**
- Since slot IDs are unpredictable, the flow is title-driven (there is no way to provide an ID)
- Dismissing an alarm through the API removes its title
- Only alarms without a title can be overwritten and a title should be set through this API to retain the ability to dismiss it without dismissing all alarms of the user
- Dismissing all alarms regardless of the title is still possible
- The title is exposed in the UI not just when the device supports it, but also when this API is enabled in the developer settings of the device itself
**Example usage:**
- https://gitlab.com/martin-braun/warpclock-plus/-/blob/feat/gb/app/src/main/java/com/antonok/warpclock/AlarmIntentService.kt demonstrates dismissing all alarms and setting multiple alarms (search for `sendBroadcast`)
- `DeviceAlarmReceiverTest` should cover all cases for a more complete overview of the capabilities of the API
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/6088
originally the 100ms sleep after writes was introduced to fix a bug where sending an appmessage right after ACKing a previous appmessage cuased the outgoing packet to be NACK'd by the 3.0+ firmware. 4fe498efc
Keeping because 100ms for most situations doesn't matter, but definitely worth avoiding when uploading new firmware to the watch.
1. We should upload all of the firmware before starting the install process. Otherwise this can cause issues with the install sometimes failing
2. Updates the encoding of the upload start. There are different upload start packet structures
3. Encode the install firmwareStart packet correctly to include how big it will be so that the watch can pre-erase the flash region.
4. Send the firmware then resouces to match the order that the pebble app sends the files.
5. remove arbitrary 100ms sleep in the main loop, the state machine blocks on readWithException already.