feat(provider): ship our own task store, vendored under our own authority
Steps 2 and 3 of docs/STORAGE-AND-SYNC.md. Agendula stops depending on a tasks provider app being installed: it now carries one. The module New :provider — the dmfs task provider 1.4.2 (Apache-2.0, DB 23), vendored in-tree, renamed to authority de.jeanlucmakiola.agendula.tasks and permissions de.jeanlucmakiola.agendula.permission.*. It coexists with OpenTasks and tasks.org rather than replacing them; nothing collides with org.dmfs.*, so both can be installed at once. The contract shape is untouched — same tables, same columns — because that is what our data layer and every CalDAV engine already speak. We own the namespace it lives in, not the schema. Vendored rather than depended on because the permission names are hardcoded in the upstream AAR's manifest and cannot be renamed in a prebuilt artifact; in-tree also satisfies F-Droid's from-source rule. provider/PROVENANCE.md records the upstream commit and every deviation, each marked with an AGENDULA CHANGE comment at the site so the list and the code cannot drift apart. The change that matters most is the account cleanup. Upstream holds GET_ACCOUNTS and deletes any task list whose account it cannot see. We dropped that permission — we only ever need our own accounts, which are visible without it — but an account we cannot see is indistinguishable from one that was removed, so left alone the provider would quietly delete synced lists. Cleanup is now restricted to account types this package authenticates itself, which is currently none. ProviderAccountCleanupTest pins that, and answers open question 3: the local path works with no account present at all. Also required by targetSdk 36, none of which upstream faced at 29: FLAG_IMMUTABLE on the notification PendingIntent, an inexact-alarm fallback so a revoked SCHEDULE_EXACT_ALARM cannot kill the app on a timezone change, and an explicit android:exported on the receiver. Storage modes ProviderResolver gains a StorageMode: LOCAL (our provider) or EXTERNAL (an installed one). Not a third SYNCED value — synced is LOCAL with an account attached, which is derived state, and modelling it as a separate store would imply switching sync on is a migration. It isn't. When the user has not chosen, the tell is whether we already hold an external provider's runtime permission. That permission is dangerous-level, so it can only be there because an earlier version asked and they agreed — the signature of an existing Posture A user, who must not be dropped onto an empty store. Fresh installs get local-first. hasPermission now short-circuits for our own provider: same-uid access bypasses the check outright, so ProviderStatus.NEEDS_PERMISSION can no longer fire in Local mode. That was the work item the storage-and-sync doc called for. The resolver's platform calls moved behind ProviderEnvironment so the decision — the part that loses people their data if wrong — is unit-tested on the JVM. Verified: 51 vendored provider tests pass, app tests pass, lintDebug and assembleDebug clean. ProviderAccountCleanupTest skips on ARM64, where Robolectric has no SQLite backend, and runs on x86_64 CI. Not yet exercised on a device. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,22 @@ androidxTestRules = "1.7.0"
|
||||
# Glance: 1.1.1 is the latest stable (1.2.0 is still rc, 1.3.0 alpha).
|
||||
glance = "1.1.1"
|
||||
|
||||
# --- :provider (vendored dmfs task provider) ---------------------------------
|
||||
# Versions the upstream 1.4.2 source was written against. These are its runtime
|
||||
# dependencies, not ours — nothing above the data layer touches them, and they
|
||||
# only move when we deliberately resync the fork. See provider/PROVENANCE.md.
|
||||
dmfsJems = "1.43"
|
||||
dmfsRfc5545Datetime = "0.2.4"
|
||||
dmfsLibRecur = "0.12.2"
|
||||
# The provider's own test suite is JUnit 4 + Robolectric, unlike the app's
|
||||
# JUnit 5. Kept as upstream wrote it (rewriting ~13 test classes would forfeit
|
||||
# the regression coverage that makes vendoring safe), but on current versions:
|
||||
# upstream pins Robolectric 3.5.1, which predates AGP's resource handling.
|
||||
robolectric = "4.16"
|
||||
junit4 = "4.13.2"
|
||||
hamcrest = "3.0"
|
||||
mockito = "5.20.0"
|
||||
|
||||
[libraries]
|
||||
# AndroidX core
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -92,8 +108,19 @@ androidx-glance-material3 = { group = "androidx.glance", name = "glance-material
|
||||
# Android tests - GrantPermissionRule
|
||||
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
|
||||
|
||||
# :provider — vendored dmfs task provider (see provider/PROVENANCE.md)
|
||||
dmfs-jems = { group = "org.dmfs", name = "jems", version.ref = "dmfsJems" }
|
||||
dmfs-jems-testing = { group = "org.dmfs", name = "jems-testing", version.ref = "dmfsJems" }
|
||||
dmfs-rfc5545-datetime = { group = "org.dmfs", name = "rfc5545-datetime", version.ref = "dmfsRfc5545Datetime" }
|
||||
dmfs-lib-recur = { group = "org.dmfs", name = "lib-recur", version.ref = "dmfsLibRecur" }
|
||||
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
|
||||
junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
|
||||
hamcrest = { group = "org.hamcrest", name = "hamcrest", version.ref = "hamcrest" }
|
||||
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||
|
||||
Reference in New Issue
Block a user