Publish the own store through a read-only OpenTasks provider (#43) (#62)
Beta — Codeberg pre-release / detect (push) Successful in 7s
Beta — Codeberg pre-release / beta (push) Skipped

In Own storage mode, Agendula now publishes its Room store as a ContentProvider that follows the OpenTasks contract. It serves `tasklists`, `tasks`, `instances` and `properties` (reminders and tags) at `<package>.tasks`, behind a dangerous-level `<package>.permission.READ_TASKS` that the user grants per app. An app that already reads OpenTasks or tasks.org only has to change the authority and the permission.

On a query, the provider lays the Room store out in OpenTasks' columns and loads that into an in-memory SQLite database, so a client's selection, arguments and sort order run as real SQL. The snapshot is rebuilt after a write, after a storage mode switch, or once it is an hour old, because instance ids and `distance_from_current` move with the clock. Observers on the authority get one notification per burst of changes. In External mode every table is empty.

Recurrence expansion moved out of `RoomTasksDataSource` into shared functions, so the app and the provider expand a series the same way.

`docs/CONTENT_PROVIDER.md` describes the contract for client developers, with the authority and permission for each build. README, CHANGELOG and the privacy policy mention the new permission.

Where this differs from the issue: the issue asked for read and write permissions, and this PR is read-only. Insert, update and delete throw. Accepting writes from other apps means deciding what happens to the fields Agendula keeps verbatim for CalDAV round-tripping, which was the issue's second open question, and that needs its own design. That is tracked in #63.

Closes #43

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/62
This commit is contained in:
Jean-Luc Makiola
2026-10-15 13:25:48 +02:00
co-authored by makiolaj
parent 35110c96ce
commit e7bf23e276
20 changed files with 1666 additions and 43 deletions
+54
View File
@@ -0,0 +1,54 @@
# Reading Agendula's tasks from another app
When Agendula keeps tasks in its own database (the default storage mode), it
publishes them through a read-only `ContentProvider` that follows the
[OpenTasks contract](https://github.com/dmfs/opentasks/blob/master/opentasks-contract/src/main/java/org/dmfs/tasks/contract/TaskContract.java).
An app that already reads OpenTasks or tasks.org can read Agendula by using a
different authority and permission.
| | |
|---|---|
| Authority | `de.jeanlucmakiola.agendula.tasks` (offline build: `de.jeanlucmakiola.agendula.offline.tasks`) |
| Permission | `de.jeanlucmakiola.agendula.permission.READ_TASKS` (offline build: `de.jeanlucmakiola.agendula.offline.permission.READ_TASKS`) |
| Protection level | `dangerous`: request it at runtime |
| Package visibility | add `<provider android:authorities="de.jeanlucmakiola.agendula.tasks" />` to your `<queries>` |
Both names are derived from the installed package name, so every build has its
own and they can be installed side by side: the authority is
`<package>.tasks` and the permission `<package>.permission.READ_TASKS`.
| Build | Package |
|---|---|
| Release | `de.jeanlucmakiola.agendula` |
| Release, offline | `de.jeanlucmakiola.agendula.offline` |
| Debug | `de.jeanlucmakiola.agendula.debug` |
| Debug, offline | `de.jeanlucmakiola.agendula.offline.debug` |
| Release test | `de.jeanlucmakiola.agendula.releasetest` |
| Release test, offline | `de.jeanlucmakiola.agendula.offline.releasetest` |
## Tables
| URI | Rows |
|---|---|
| `content://<authority>/tasklists` | one per list |
| `content://<authority>/tasks` | one per task; a modified occurrence of a recurring task is its own row with `original_instance_id` / `original_instance_time` set |
| `content://<authority>/instances` | one per occurrence: recurring tasks are expanded up to one year back and two years ahead, and modified occurrences replace the ones they override |
| `content://<authority>/properties` | reminders (`vnd.android.cursor.item/alarm`) and tags (`vnd.android.cursor.item/category`) |
Each table also accepts `/<id>`. Column names, value encodings (epoch millis,
all-day dates as UTC midnight, `status`, `priority`, `class`) and MIME types
are the OpenTasks ones. `selection`, `selectionArgs` and `sortOrder` work as
they do against OpenTasks.
## Behaviour
- **Read-only.** `insert`, `update` and `delete` throw
`UnsupportedOperationException`.
- **Change notifications.** Register a `ContentObserver` on
`content://<authority>` with `notifyForDescendants = true`. It fires when
tasks change or when the user switches storage mode.
- **External storage mode.** When the user has Agendula work on top of
OpenTasks or tasks.org, every table is empty. Read that provider directly.
- **Instance ids** are not stable: as in OpenTasks they are reassigned when the
data changes, and also hourly as the expansion window moves (observers are
notified then too). Key on `task_id` plus `instance_original_time`.