Let other apps write to the own store through the provider (#63)
This commit is contained in:
1 parent
e7bf23e276
commit
522d5ca35e
22 files changed
+2250
-66
No files matched your search
+72
-13
@@ -1,21 +1,23 @@
|
||||
# Reading Agendula's tasks from another app
|
||||
# Using 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
|
||||
publishes them through a `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.
|
||||
An app that already works with OpenTasks or tasks.org can work with Agendula by
|
||||
using a different authority and permissions.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 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 |
|
||||
| Read permission | `de.jeanlucmakiola.agendula.permission.READ_TASKS` (offline build: `de.jeanlucmakiola.agendula.offline.permission.READ_TASKS`) |
|
||||
| Write permission | `de.jeanlucmakiola.agendula.permission.WRITE_TASKS` (offline build: `de.jeanlucmakiola.agendula.offline.permission.WRITE_TASKS`) |
|
||||
| Protection level | `dangerous`: request them 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`.
|
||||
`<package>.tasks` and the permissions are `<package>.permission.READ_TASKS` and
|
||||
`<package>.permission.WRITE_TASKS`.
|
||||
|
||||
| Build | Package |
|
||||
|---|---|
|
||||
@@ -40,15 +42,72 @@ 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.
|
||||
|
||||
## Writing
|
||||
|
||||
Writes need the write permission and go through the same code as edits made in
|
||||
Agendula itself: a changed task is marked for the next CalDAV sync, reminders
|
||||
are re-armed, and widgets refresh.
|
||||
|
||||
| Table | insert | update | delete |
|
||||
|---|---|---|---|
|
||||
| `tasklists` | no | no | no |
|
||||
| `tasks` | yes | yes | yes |
|
||||
| `instances` | no | see below | one occurrence |
|
||||
| `properties` | alarms and tags | alarms and tags | alarms and tags |
|
||||
|
||||
`update` and `delete` take a `selection` like a query does, and it matches the
|
||||
rows you can read. `insert` returns the new row's URI. `applyBatch` runs in one
|
||||
transaction: if any operation fails, none of them is applied. A write that
|
||||
changes nothing does not mark the task for sync.
|
||||
|
||||
**Columns.** A task write may set `list_id`, `_uid` (on insert only), `title`,
|
||||
`description`, `location`, `url`, `priority`, `class`, `status`,
|
||||
`percent_complete`, `completed`, `task_color`, `dtstart`, `due`, `duration`,
|
||||
`is_allday`, `tz`, `rrule`, `rdate`, `exdate`, `parent_id` and `sorting`. Other
|
||||
columns the table publishes are ignored, so you can write back a row you read.
|
||||
A column the table does not have is rejected with `IllegalArgumentException`.
|
||||
Agendula's sync state (the server's URL and ETag, the iCalendar `SEQUENCE`, and
|
||||
properties it does not understand) is not published and cannot be written.
|
||||
|
||||
**Status.** `status`, `percent_complete` and `completed` stay consistent the way
|
||||
OpenTasks keeps them. Completing a task sets 100 % and a completion time,
|
||||
progress between 1 and 99 sets "in process", and a completion time alone
|
||||
completes the task.
|
||||
|
||||
**Recurring tasks.** Writing `status`, `percent_complete` or `completed` to an
|
||||
`instances` row completes, cancels or reopens that one occurrence; partial
|
||||
progress is rejected. Other content columns written to an instance change the
|
||||
whole series, and that occurrence too if it had been modified on its own.
|
||||
Changing a time or recurrence column (`dtstart`, `due`, `duration`,
|
||||
`is_allday`, `tz`, `rrule`, `rdate`, `exdate`) is rejected on an instance of a
|
||||
recurring task, so move a series through `tasks`; writing those columns back
|
||||
unchanged, as in a row you read, is fine. Moving a series' start or changing its
|
||||
rule drops the occurrences that had been modified on their own, as it does in
|
||||
the app. Deleting an `instances` row deletes that one occurrence.
|
||||
|
||||
**Subtasks.** A subtask has to be in its parent's list. Moving a task to another
|
||||
list moves its subtasks with it, and deleting a task deletes them. A task in a
|
||||
synced list stays as a hidden tombstone until the next sync removes it from the
|
||||
server.
|
||||
|
||||
**Rejected writes** throw `IllegalArgumentException` for a bad value, a missing
|
||||
list or parent, or a parent in another list. They throw
|
||||
`UnsupportedOperationException` for lists and for tasks in a list the server
|
||||
has made read-only, and `IllegalStateException` while Agendula works on top of
|
||||
an external provider or is still starting. `list_access_level` does not tell
|
||||
you which lists are read-only: OpenTasks reserves it and asks providers to
|
||||
publish `0`.
|
||||
|
||||
## 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`.
|
||||
OpenTasks or tasks.org, every table is empty and writes are refused. Use that
|
||||
provider directly.
|
||||
- **Ids are stable.** A task keeps its `_id`. An instance's `_id` is built
|
||||
from its series and its original time, and a property's from its alarm or
|
||||
its task and tag, so they stay the same across reads and within a batch for
|
||||
as long as that occurrence, alarm or tag exists. `distance_from_current`
|
||||
still moves with the clock, and observers are notified hourly when it does.
|
||||
+16
-8
@@ -55,7 +55,8 @@ All of this is verifiable in the
|
||||
- **On your device (the default)** — your task lists, tasks and reminders are
|
||||
kept in Agendula's own database inside the app's private storage, and
|
||||
uninstalling the app removes it. Another app can read these tasks only if you
|
||||
grant it Agendula's "read Agendula tasks" permission (see section 9).
|
||||
grant it Agendula's "read Agendula tasks" permission, and change them only if
|
||||
you grant it "add, change and delete Agendula tasks" (see section 9).
|
||||
- **In a tasks provider you already use** — OpenTasks or tasks.org. Agendula
|
||||
then reads and writes that app's task database through Android's provider
|
||||
mechanism, after you grant its read/write permission. Whatever already
|
||||
@@ -210,13 +211,20 @@ source:
|
||||
used for scheduled background work (WorkManager); on older Android versions
|
||||
it needs them to run an expedited sync.
|
||||
|
||||
Agendula declares one permission that other apps can request:
|
||||
`de.jeanlucmakiola.agendula.permission.READ_TASKS`. Android asks you before
|
||||
granting it, and you can revoke it at any time in the system settings. An app
|
||||
that holds it can read your task lists, tasks, reminders and tags from
|
||||
Agendula's own database (read-only, and nothing while Agendula works on top of
|
||||
an external tasks provider). What that app then does with them is governed by
|
||||
its own privacy policy.
|
||||
Agendula declares two permissions that other apps can request. Android asks you
|
||||
before granting either, and you can revoke them at any time in the system
|
||||
settings.
|
||||
|
||||
- `de.jeanlucmakiola.agendula.permission.READ_TASKS`: an app that holds it can
|
||||
read your task lists, tasks, reminders and tags from Agendula's own database.
|
||||
- `de.jeanlucmakiola.agendula.permission.WRITE_TASKS`: an app that holds it can
|
||||
add, change, complete and delete those tasks, reminders and tags. It cannot
|
||||
change or delete task lists. Its changes sync to your CalDAV server like your
|
||||
own edits.
|
||||
|
||||
Neither gives access to anything while Agendula works on top of an external
|
||||
tasks provider. What an app does with your tasks is governed by its own privacy
|
||||
policy.
|
||||
|
||||
## 10. Distribution channels
|
||||
|
||||
|
||||
Reference in new issue
Block a user