Files
Gadgetbridge/app/src/main/res/layout/item_with_details.xml
T
Martin BraunandJosé Rebelo b8a85e03ea Add Third Party Alarm Intent API: Set and Dismiss alarms for your device from any app (#6088)
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
2026-05-21 19:57:07 +02:00

71 lines
2.5 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<ImageView
android:id="@+id/item_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="false"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:contentDescription="Item preview" />
<ImageView
android:id="@+id/item_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_below="@+id/item_preview"
android:contentDescription="@string/candidate_item_device_image"
android:padding="4dp" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/item_preview"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/item_image"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="false"
android:textStyle="bold"
tools:text="Item Name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
tools:text="Item Description" />
<TextView
android:id="@+id/item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text=""
android:textColor="@color/design_default_color_error" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>