Built in tasksprovider - #2750
Open
timofurrer wants to merge 6 commits into
Open
Conversation
Phase 1 (provider core) of the DAVx5 VTODO tasks provider design (doc/tasks-provider.md): - tasks-provider-contract: dependency-free public contract (URIs, columns, mimetypes, permissions, intent-discovery action) that third-party frontends depend on. - tasks-provider: Room schema (tasks.db, no destructive fallback) + DavTasksProvider ContentProvider (URI matching, strict query builder, column allow-listing, dirty propagation, tombstones, batch/bulk in a transaction, notifyChange). Insert defaults fill in NOT NULL columns the caller didn't supply, the way other Android providers (e.g. CalendarProvider2) handle optional columns. RRULE/Instances expansion (Phase 3) and attachment blob storage (Phase 4) are not yet implemented; the Instances table schema exists so frontends can already target it. Includes instrumented security/semantics tests against a real (test-registered) DavTasksProvider instance: column allow-listing (SQL injection defense), dirty-flag semantics for sync-adapter vs. non-sync-adapter callers, tombstones vs. real deletes, cascade-on-delete, and the Instances table's read-only enforcement.
Includes android:description for the READ/WRITE permissions' grant dialog, so a third-party frontend's permission request doesn't read as vague/ suspicious (no supporting text) to the user granting it.
DavTaskList/DavTaskListProvider/DavRecurringTaskList mirror the DMFS backend's storage/tasks classes, operating against the DAVx5-hosted provider via ContentProviderClient. DavTaskBuilder/DavTaskHandler map VTODO <-> Entity with full fidelity where the DMFS backend is lossy: ORGANIZER CN/SENT-BY, full ATTENDEE parameters, RFC 9253 RELTYPE values, CONTACT/RESOURCES/REQUEST-STATUS, and VALARM stored as TRIGGER/DURATION/REPEAT/ACTION instead of collapsed to minutes_before. Recurrence exceptions are dropped (logged), matching the DMFS backend's existing bitfireAT#2357 limitation - full RECURRENCE-ID override support is Phase 3. Inline BINARY attachments are dropped (logged) - blob storage is Phase 4. Includes instrumented CRUD tests for DavTaskList/DavTaskListProvider (covering the two-table Properties/Alarms sub-row routing that's new relative to the DMFS backend's single Properties table) and Robolectric tests for DavTaskBuilder/DavTaskHandler. PropertyList.add() in ical4j is immutable and returns a copy, so the plusAssign helper for it was a no-op - alarm trigger/description/etc. never made it into the built VALARM. Build the property list in one shot instead. Also drop the broken plusAssign helper so it can't be reused.
LocalDavTask/LocalDavTaskList/LocalDavTaskListStore implement the existing LocalResource/LocalCollection/LocalDataStore interfaces on top of storage/davtasks, mirroring the DMFS-backed LocalTask/LocalTaskList/ LocalTaskListStore. DavTasksSyncManager/DavTaskSyncer mirror TasksSyncManager/TaskSyncer for VTODO CalDAV sync. TasksAppManager recognizes the built-in provider's (runtime-discovered, D1) authority and routes getDataStore()/BaseSyncWorker's dispatch to it. SyncDataType.currentAuthority()/fromAuthority() do the same. AutomaticSyncManager's enable/disable loops and provider-selection check are extended the same way, since they don't fit the TaskProvider.ProviderName model the three external providers use. A dedicated DavTasksSyncAdapterService + res/xml/sync_davtasks.xml register the actual SyncAdapterType Android's content-triggered sync mechanism needs - without it, notifyChange(syncToNetwork = true) has nothing to route to, no matter how AutomaticSyncManager is wired. Verified end-to-end on a real device against a real CalDAV server: a third-party frontend's local write triggers an automatic sync via the registered adapter, uploads through DavTasksSyncManager, and clears dirty. Known follow-up: no Settings UI card yet to let a user pick the built-in provider (only the 3 external ones are listed today).
Adds a fourth option ("DAVx5 (built-in)") to the existing Tasks settings
screen, alongside jtx Board/tasks.org/OpenTasks - closes the gap flagged
in the previous commit where the built-in provider had no way to actually
be selected from the app.
The switch on that row is disabled/always-on rather than interactive: it
has nothing to install (unlike the three external providers), so making it
look like a live toggle was misleading - confirmed by a real user tapping it
expecting it to do something. Re-selecting an already-selected built-in
option always re-applies (rather than no-op'ing) so it can be used to force
a re-run of the automatic-sync setup, e.g. after an app update.
Also fixes TasksAppWatcher, which runs on every app start: it only knew
about the three external providers, so it saw the newly-selectable built-in
provider as "nothing selected" and cleared it right back out - reproduced
via a manual sync request waking the app process, which runs the watcher
before the sync worker gets to read the (about to be cleared) setting.
TaskInstances was a stub - not even non-recurring tasks got a row, since nothing ever called TaskInstanceDao.insert(). Add RecurrenceExpander (pure, ical4j Recur-based: RRULE ∪ RDATE − EXDATE anchored on DTSTART, RECURRENCE-ID overrides folded in by matching original_instance_time) and InstanceMaintainer, which re-derives a task's whole family (main + exceptions) from scratch and is wired into DavTasksProvider.insert/update/delete. Deliberately bounded and deterministic instead of a rolling time window: expansion stops at whichever of the RRULE's own COUNT/UNTIL, 500 occurrences, or 10 years past DTSTART comes first - cheap to fully re-derive on every write, no background job needed. Documented on TaskInstances/RDATE/EXDATE so it's not a silent surprise. tasks-provider gets its own ical4j dependency rather than depending on synctools, which wraps ical4j with sync-engine concerns this module has no business knowing about; needed the same strict Apache Commons pins synctools uses to avoid a transitive version conflict on synctools' androidTest classpath (it depends on this module to test DavTasksProvider against a real instance).
timofurrer
force-pushed
the
built-in-tasksprovider
branch
from
August 6, 2026 09:29
1da5306 to
71761bb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related: #2740
Checklist
Here's the filled-out template as raw markdown, ready to copy into the PR description:
Purpose
Implements a first version of the DAVx⁵-hosted VTODO tasks provider proposed in discussion #2740
(design doc:
doc/tasks-provider.md).Today, DAVx⁵'s task sync only works through third-party providers (OpenTasks, tasks.org, jtx
Board), each built on a lossy DMFS-based Android contract — dropped ORGANIZER parameters,
collapsed VALARM (just
minutes_before+ a reference enum), opaque ATTENDEE/ATTACH/CONTACT/RESOURCES/REQUEST-STATUS, no RFC 9253 task-dependency support, and dropped RECURRENCE-ID
overrides (#2357). This PR adds a DAVx⁵-hosted
ContentProviderwith a full-fidelity contractthat any third-party frontend can build against, with no direct DAVx⁵ dependency (see §1 of the
design doc for the full traceability matrix).
Short description
tasks-provider-contract: a dependency-free public contract (TaskLists,Tasks,TaskProperties,TaskAlarms,TaskInstances) for third-party frontends, discovered atruntime via an intent action (D1) so the OSE/Managed/Selectable DAVx⁵ variants can coexist.
tasks-provider: a Room-backedContentProvider(DavTasksProvider) implementing thecontract — strict-mode query builder, column allow-listing (SQL injection defense), dirty-flag
propagation from sub-rows to their parent task, tombstones vs. physical deletes, transactional
batch/bulk operations.
synctools/storage/davtasksandsynctools/mapping/davtasks: storage/mapping layersmirroring the existing DMFS backend's structure, with full-fidelity VTODO↔ContentValues mapping
(ORGANIZER CN/SENT-BY, full ATTENDEE parameters, RFC 9253 RELTYPE values, full VALARM fields
instead of the DMFS
minutes_beforecollapse).LocalDavTaskList,DavTasksSyncManager,DavTaskSyncer,TasksAppManager,AutomaticSyncManager) alongside the existingOpenTasks/tasks.org/jtx paths — no changes to the shared sync logic itself.
choices.
TaskInstances, including RECURRENCE-ID overridefolding (
RecurrenceExpander/InstanceMaintainer) — bounded and deterministic (the RRULE's ownCOUNT/UNTIL, 500 occurrences, or 10 years past DTSTART, whichever comes first) rather than a
rolling time window, so it's cheap to fully re-derive on every write.
Robolectric/JVM unit tests for the mapping and recurrence-expansion layers.
Not yet implemented: attachment blob storage (
openFile), a client library/sample frontend, andmigration tooling from the existing third-party providers — see the Phasing table in
doc/tasks-provider.mdfor full scope and current status per phase.Checklist
The PR has a proper title, description and label.
I have self-reviewed the PR.
I have added documentation to complex functions and functions that can be used by other modules.
I have added reasonable tests or consciously decided to not add tests.
Purpose
Implements a first version of the DAVx⁵-hosted VTODO tasks provider proposed in discussion Generic/pluggable task provider support (like CalendarContract/ContactsContract, but for tasks) #2740
(design doc:
doc/tasks-provider.md).Today, DAVx⁵'s t