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:
@@ -16,6 +16,29 @@
|
||||
<uses-permission android:name="org.tasks.permission.READ_TASKS" />
|
||||
<uses-permission android:name="org.tasks.permission.WRITE_TASKS" />
|
||||
|
||||
<!-- CalDAV sync. ACCESS_NETWORK_STATE is merged in by work-runtime anyway,
|
||||
but it shows in F-Droid's permission diff, so declare it deliberately
|
||||
rather than letting it appear from nowhere.
|
||||
|
||||
READ_SYNC_SETTINGS / WRITE_SYNC_SETTINGS are what the ContentResolver
|
||||
sync APIs need. No FOREGROUND_SERVICE: sync is a plain worker, and the
|
||||
dataSync FGS type would bring the Android 15 six-hours-per-24 budget
|
||||
(whose failure mode is a fatal RemoteServiceException) and a Play
|
||||
requirement for a video demo.
|
||||
|
||||
Two more permissions appear in the merged manifest without being
|
||||
declared here, and both come from work-runtime: WAKE_LOCK, and
|
||||
FOREGROUND_SERVICE. The latter is not us taking the FGS route — below
|
||||
API 31 WorkManager implements expedited work with a foreground service,
|
||||
and minSdk is 29, so it is load-bearing for the "Sync now" button.
|
||||
Removing it with tools:node="remove" would break expedited work on
|
||||
exactly the older devices that need it most. Noted because it shows in
|
||||
F-Droid's permission diff and would otherwise look unexplained. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<!-- Exact due-time reminders: USE_EXACT_ALARM on 33+, SCHEDULE on 31-32. -->
|
||||
@@ -43,6 +66,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Agendula"
|
||||
@@ -95,6 +119,57 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Sync plumbing. The stub provider exists only to give the sync
|
||||
adapter an authority to register against: Agendula publishes no real
|
||||
ContentProvider since :provider was deleted, and without an authority
|
||||
ContentService.hasAuthorityAccess() makes every ContentResolver sync
|
||||
call a silent no-op at targetSdk >= 34. -->
|
||||
<provider
|
||||
android:name=".data.sync.SyncStubProvider"
|
||||
android:authorities="${applicationId}.sync"
|
||||
android:exported="false"
|
||||
android:syncable="true" />
|
||||
|
||||
<!-- Exported and guarded by ACCOUNT_MANAGER. Note that
|
||||
android.permission.ACCOUNT_AUTHENTICATOR does not exist. -->
|
||||
<service
|
||||
android:name=".data.sync.AuthenticatorService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.ACCOUNT_MANAGER">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".data.sync.SyncAdapterService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_SYNC_ADAPTER">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_adapter" />
|
||||
</service>
|
||||
|
||||
<!-- WorkManager's on-demand initialisation. Removing the default
|
||||
initializer is what lets AgendulaApp supply a HiltWorkerFactory, so
|
||||
@HiltWorker workers can take injected dependencies. -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<!-- Persists the per-app language on API < 33, where the platform
|
||||
per-app-languages API is unavailable. On 33+ this is a no-op. -->
|
||||
<service
|
||||
|
||||
Reference in New Issue
Block a user