sync(chunk 2c): Keystore credentials, AccountManager, stub sync adapter
The platform half of chunk 2; the account-add UI is 2d, since it is a design task and the piece that needs an on-device review. A stub ContentProvider turned out to be required and was not in the plan: a sync adapter registers against a content authority, and we publish no provider since :provider was deleted. Without one there is nothing for contentAuthority to name, nothing for requestSync to address, and hasAuthorityAccess() makes every ContentResolver sync call a silent no-op at targetSdk 34+. - CredentialStore: Keystore AES/GCM, blob in its own DataStore file. security-crypto is formally deprecated and terminal. Decryption failure means re-authenticate, never a crash — including ProviderException, which is a RuntimeException and escapes the obvious catches. - CalDavAccounts + SyncAuthenticator: no password reaches AccountManager, which stores them as plain TEXT. The authenticator never returns null — a null is the protocol for "answering asynchronously", and nothing here does, so Settings would wait forever. addAccount refuses with a readable message until 2d ships the screen, rather than opening the home screen and hanging. - SyncAdapterService: enqueue and wait on the unique work *name*, not the request id — enqueueUniqueWork is async so the id is unknown when the wait starts, and under KEEP it may never exist at all. Being deduplicated is not a failure. - Account type and authority are per build variant, so debug and release do not fight over ownership. SyncContractTest guards the Kotlin/resValue pair. - The credential blob is the only thing excluded from backup: Keystore keys are non-exportable, so a restored ciphertext can never be decrypted. Known trade-off recorded in network_security_config.xml and SYNC-PLAN.md: the user CA store is trusted for all traffic, which chunk 5's cert4android should replace rather than sit beside. The instrumented tests here compile but have not been run — device work waits for an explicit go-ahead.
This commit is contained in:
@@ -33,6 +33,14 @@ android {
|
||||
versionName = "0.4.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// The sync-adapter and authenticator XML descriptors cannot read
|
||||
// BuildConfig, so the two identifiers they need are generated here.
|
||||
// Derived from applicationId so the debug and releaseTest builds get
|
||||
// their own and can be installed alongside the real app without their
|
||||
// accounts colliding. Must stay in step with SyncContract.
|
||||
resValue("string", "account_type", "de.jeanlucmakiola.agendula.caldav")
|
||||
resValue("string", "sync_authority", "de.jeanlucmakiola.agendula.sync")
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -66,6 +74,8 @@ android {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
isMinifyEnabled = false
|
||||
resValue("string", "account_type", "de.jeanlucmakiola.agendula.debug.caldav")
|
||||
resValue("string", "sync_authority", "de.jeanlucmakiola.agendula.debug.sync")
|
||||
}
|
||||
// A locally-installable twin of `release`: same R8 shrinking + obfuscation
|
||||
// and resource shrinking, but debug-signed and given its own applicationId
|
||||
@@ -82,6 +92,8 @@ android {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
matchingFallbacks += "release"
|
||||
resValue("string", "account_type", "de.jeanlucmakiola.agendula.releasetest.caldav")
|
||||
resValue("string", "sync_authority", "de.jeanlucmakiola.agendula.releasetest.sync")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +105,10 @@ android {
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
// The account type and sync authority are generated per variant so the
|
||||
// debug and releaseTest builds do not fight the real app over ownership
|
||||
// of an account type. AGP 9 requires opting in.
|
||||
resValues = true
|
||||
}
|
||||
|
||||
// Don't embed AGP's dependency-metadata block in the APK signing block. It's
|
||||
@@ -177,6 +193,13 @@ dependencies {
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
// Sync runs in WorkManager, triggered *through* the sync-adapter framework.
|
||||
// hilt-work supplies the HiltWorkerFactory; its compiler generates the
|
||||
// @HiltWorker plumbing.
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.hilt.work)
|
||||
ksp(libs.androidx.hilt.compiler)
|
||||
|
||||
// RFC 5545 recurrence expansion, in-process. Pinned at 0.12.2 — 0.16.0
|
||||
// removed RecurrenceSet. rfc5545-datetime comes with it and is part of its
|
||||
// API surface, so it isn't declared separately.
|
||||
@@ -184,6 +207,8 @@ dependencies {
|
||||
|
||||
// Vendored dav4jvm — the CalDAV protocol layer. See dav/PROVENANCE.md.
|
||||
implementation(project(":dav"))
|
||||
// Discovery, auth and Nextcloud Login Flow v2.
|
||||
implementation(project(":caldav"))
|
||||
// :dav gets org.xmlpull.v1 from the Android framework at runtime and declares
|
||||
// xpp3 compileOnly, which is not transitive. Unit tests run on a plain JVM
|
||||
// with no framework, and android.jar's stub factory returns null under
|
||||
|
||||
Reference in New Issue
Block a user